Wikilivres
frwikibooks
https://fr.wikibooks.org/wiki/Accueil
MediaWiki 1.47.0-wmf.19
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
Japonais/Grammaire/Particules
0
2785
772009
682614
2026-09-11T19:49:20Z
~2026-49266-35
124543
/* Particules principales */
772009
wikitext
text/x-wiki
{{Niveau intermédiaire}}
Les particules (ou enclitiques) sont des mots invariables, en général assez courts, qui jouent le rôle d'indicateurs grammaticaux au sein de la phrase. Elles sont accolées à la fin des mots ou des groupes de mots qu'elles qualifient. Tous les mots du japonais (noms, adjectifs, verbes…) peuvent être affectés d'une ou plusieurs particules.
Les particules servent en particulier à indiquer la fonction des groupes dans la phrase, et il y a donc une particule pour indiquer le sujet, une autre pour indiquer le complément d'objet, etc. Les particules ont cependant un usage plus général et servent également à indiquer les relations des phrases entre elles (par exemple la cause, l'opposition) ou la nuance que le locuteur apporte à une phrase (exclamation, supposition, prévision).
= Particules principales =
{| class="wikitable"
|+ '''Types de particules'''
|-
!Utilisation classique
!Nom japonais
!Rōmaji
!Nom français
!Exemples
|-
|Après un substantif
|格助詞
|kakujoshi
|particule de cas
|「が」・「を」・「に」・「で」・「から」・「へ」・「と」・「の」
|-
|Par exemple après un nom
|取り立て助詞
|toritate joshi
|particule de thématisation
|「は」・「も」・「でも」・「しか」・「だけ」・「ばかり」・「まで」・「ぐらい」
|-
|À la place d'un nom
|準体助詞
|juntai joshi
|particule de nominalisation
|「の」
|-
|Liens entre deux mots
|並列助詞
|heiretsu joshi
|particule d'énumération
|「や」・「と」・「か」・「とか」
|-
|Lien entre deux phrases
|接続助詞
|setsuzoku joshi
|particule de coordination
|「が」・「から」・「ので」・「のに」・「と」・「ながら」
|-
|En fin de groupe
|間投助詞
|kantojoshi
|particule de communication
|「ね」・「よ」・「さ」
|-
|En fin de phrase
|終助詞
|shujoshi
|particule finale
|「か」・「な」・「わ」・「ぞ」・「ぜ」•「ね」•「よ」•「さ」
|}
=== La particule de thématisation 「は」 ===
La particule de thématisation (ou de topicalisation) indique le '''thème''' de la phrase. Elle est placée après le groupe nominal qui est thématisé, et se note '''は''' (''ha''). Attention. Elle se prononce '''わ''' (''wa'') et s'écrit ''wa'' en Hepburn.
Cette notion n'a ''a priori'' rien à voir avec celle de sujet du verbe. Il est en effet possible de thématiser n’importe quel groupe nominal de la phrase pour montrer sur quel élément on cherche à donner des informations.
{|class="wikitable"
|-
!Groupe thématisé
!Phrase japonaise
!Rōmaji
!Traduction
|-
|rowspan="2"|sujet
|わたし'''は'''パンを食べます。
|Watashi '''wa''' pan o tabemasu.
|<u>Je</u> mange du pain.
|-
|田中さん'''は'''本を読みます。
|Tanaka-san '''wa''' hon o yomimasu.
|<u>M./M<sup>me</sup>/M<sup>lle</sup> Tanaka</u> lit un livre.
|-
|complément d'objet direct
|この本'''は'''、読んだことがありますか ?
|Kono hon '''wa''', yonda koto ga arimasu ka ?
|« Avez-vous lu <u>ce livre</u> ? »<br>Littéralement : « Ce livre, vous l'avez lu ? »
|-
|complément circonstanciel de temps
|きょう'''は'''、良いお天気ですね ?
|Kyō '''wa''', ī otenki desu ne ?
|« Il fait beau <u>aujourd'hui</u>, n'est-ce-pas ? »<br>Littéralement : « Aujourd'hui, il fait beau, n'est-ce pas ? »
|-
|complément circonstanciel de lieu
|大学に'''は'''学生がいます。
|Daigaku ni '''wa''' gakusei ga imasu.
|Il y a des étudiants <u>dans les universités</u>.
|}
=== La particule sujet 「が」 ===
La particule '''が''' (ga) permet d'indiquer le '''sujet grammatical''' de la phrase. Pour les débutants, cette particule est parfois difficile à distinguer de la particule '''は''' (wa) qui indique le '''thème''' de la phrase.
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|雨'''が'''降っています。
|Ame '''ga''' futte imasu.
|La pluie tombe.
|-
|これ'''が'''酷い。
|Kore '''ga''' hidoi.
|C'est atroce.
|}
<u>Remarque :</u> il faut faire attention à ne pas faire de confusion avec le français. De nombreuses tournures de phrases en japonais diffèrent notablement du français et entraînent des confusions de particules. Par exemple, pour exprimer le désir, on peut dire :
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|この赤いりんご'''が'''欲しいです。
|Kono akai ringo '''ga''' hoshī desu.
|littéralement : « Cette pomme rouge est désirée. »
|}
* <u>Remarque :</u>
:# mais la tournure normale en français est : « Je veux cette pomme rouge. » et les francophones ont donc tendance à utiliser '''を''' au lieu de '''が'''.
:# dans une proposition subordonnée relative, on peut souvent remplacer le '''が''' sujet de cette proposition par un '''の'''.
=== La particule objet 「を」 ===
La particule objet '''を''' marque le '''complément d'objet direct du verbe'''.
En japonais moderne, à l'exception de quelques régions du Japon, il n'y a plus aucune différence de prononciation entre '''を''' et '''お'''. '''を''' se prononce donc '''お''' bien que retranscrite ''wo'' en rōmaji.
{|class="wikitable"
|-
!Utilisation
!Phrase japonaise
!Rōmaji
!Traduction
|-
|Complément d'objet direct
|イチゴ'''を'''食べます。
|Ichigo '''o''' tabemasu.
|Je mange une fraise.
|-
|Lieu par lequel on passe
|公園'''を'''行きます。
|Kōen '''o''' ikimasu.
|Je passe par le parc.
|-
|Origine d'un mouvement
|公園'''を'''出ます。
|Kōen '''wo''' demasu.
|Je sors du parc.
|-
|Cause d'une émotion humaine
|ジャンは父の死'''を'''悲しみました。
|Jan wa chichi no shi '''wo''' kanashimimashita.
|Jean a été attristé par la mort de son père.
|}
=== La particule de but 「に」 ===
{|class="wikitable"
|-
!Utilisation
!Phrase japonaise
!Rōmaji
!Traduction
|-
|Lieu ponctuel d'existence
|家'''に'''猫がいます。
|Uchi '''ni''' neko ga imasu.
|Il y a un chat chez moi.
|-
|But d'un mouvement (Cas allatif)
|日本'''に'''行きたいです。
|Nihon '''ni''' ikitai desu.
|Je veux aller au Japon.
|-
|Complément d'objet indirect (COI)
|妹は私'''に'''りんごをくれました。
|Imōto wa watashi '''ni''' ringo o kuremashita.
|Ma petite sœur m'a donné une pomme.
|-
|But d'une action
|デパートへ野菜を買い'''に'''行きます。
|Depāto e yasai o kai '''ni''' ikimasu.
|Je vais au magasin acheter des légumes.
|-
|Support d'une action
|紙'''に'''羊、を書いてください。
|Kami '''ni''' hitsuji, o kaite kudasai.
|Dessine un mouton sur le papier, s'il te plaît.
|-
|Agent dans les phrases passives
|私は弟'''に'''りんごを食べられました。
|Watashi wa otōto '''ni''' ringo o taberaremashita.
|La pomme a été mangée par mon frère.
|-
|Agent dans les phrases causatives
|私は弟'''に'''りんごを食べさせます。
|Watashi wa otōto '''ni''' ringo o tabesasemasu.
|Je fais manger des pommes à mon petit frère.
|}
=== La particule de direction 「へ」 ===
Indique la direction vague vers laquelle on va. Attention ! Elle se prononce ''え'' (''e'') et s'écrit ''e'' en Hepburn.
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|日本'''へ'''行きたい。
|Nihon '''e''' ikitai.
|Je veux aller au Japon.
|-
|ロビンとマリアンはシャーウッドの森'''へ'''逃げ出す。
|Robin to Marian wa Shāuddo no mori '''e''' nigedasu.
|Robin et Marianne s'enfuient vers la forêt de Sherwood.
|}
=== La particule de possession 「の」 ===
La particule '''の''' (no) permet de marquer la relation de possession avec la syntaxe :
'''Possesseur + の + Possédé'''
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|隣'''の'''車は青いです。
|Tonari '''no''' kuruma wa aoi desu.
|La voiture du voisin est bleue.
|}
<u>Remarque :</u> le possédé peut être lui-même possesseur et réaliser ainsi une suite de plusieurs '''の''' successifs. 「空の王国の王女の城の庭の花の花弁の色だ。」 (Sora no ōkoku no ōjo no shiro no niwa no hana no hanabira no iro da.) « C'est la couleur des pétales de la fleur du jardin du château de la princesse du royaume du ciel. »
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|隣'''の'''家'''の'''窓を閉じました。
|Tonari '''no''' ie '''no''' mado wo tojimashita.
|J’ai fermé les fenêtres de la maison du voisin.
|}
<u>Remarque :</u> l’ordre des mots dans la relation ''possesseur-possédé'' est exactement '''inverse de celui du français'''.
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|妖精'''の'''ナビィだ。<br>熊'''の'''プーさんだ。<br>コピー忍者'''の'''カカシだ。<br>恐ろしい'''の'''森だ。<br>「新宿'''の'''種馬」だ。<br>「新宿'''の'''一発狼」だ。<br>「恐怖'''の'''もっこり男」だ。
|Yōsei '''no''' Nabī da.<br>Kuma '''no''' Pū-san da.<br>Kopī ninja '''no''' Kakashi da.<br>Osoroshī '''no''' mori da.<br>« Shinjuku '''no''' taneuma » da.<br>« Shinjuku '''no''' ichihatsu ōkami » da.<br>« Kyōfu '''no''' mokkori otoko » da.
|C'est Navi la fée.<br>C'est Winnie l'ourson.<br>C'est Kakashi le ninja copieur.<br>C'est la forêt terrible.<br>C'est « l'étalon de Shinijuku ».<br>C'est « le loup d'un coup de Shinjuku ».<br>C'est « l'homme à l'effrayante érection ».
|}
<u>Remarque :</u> Ici, '''の''' signifie « qui est ».
=== La particule de moyen 「で」 ===
La particule '''で''' (de) permet en particulier de marquer '''le lieu de l'action''' ou l''''instrument''' permettant l'action. Cet emploi ne doit pas être confondu avec celui de la particule '''に'''. En japonais, on distingue grammaticalement le lieu où quelque chose '''existe''' (particule '''に''') et le lieu où l’on '''réalise''' une action (particule '''で''').
{|class="wikitable"
|-
!Utilisation
!Phrase japonaise
!Rōmaji
!Traduction
|-
|Lieu où se passe une action
|だいどころ'''で'''御飯を作っています。
|Daidokoro '''de''' gohan wo tsukutte imasu.
|Je suis en train de faire le repas dans la cuisine.
|-
|Complément de moyen (cas instrumental)
|お兄さんはバス'''で'''大学に行きます。
|Onīsan wa basu '''de''' daigaku ni ikimasu.
|Mon grand frère va à l'université en bus.
|-
|Complément de cause
|病気'''で'''林檎を食べません。
|Byōki '''de''' ringo wo tabemasen.
|Je ne mange pas de pomme à cause de ma maladie.
|-
|Complément de temps écoulé
|十五分'''で'''林檎を食べました。
|Jūgofun '''de''' ringo wo tabemashita.
|J’ai mangé la pomme en 15 minutes.
|}
* <u>Remarque :</u> sur la dernière utilisation : là aussi, il faut opposer '''で''' à '''に''', car il s'agit ici d'un complément de temps pris dans sa globalité (= pendant).
=== Particules interrogatives ===
Comme en français, le caractère interrogatif de la phrase peut être marqué par une simple accentuation montante (en particulier au style neutre), mais on peut aussi utiliser des particules qui permettent alors d'apporter de nombreuses nuances à la question.
{|class="wikitable"
|-
!Particule
!Phrase japonaise
!Romaji
!Traduction
|-
|か
|何をしています'''か'''?
|Nani wo shite imasu '''ka''' ?
|Que faites vous ?
|-
|pas de particule
|何をしてる?
|Nani wo shiteru ?
|Que fais tu ?
|-
|の
|どうして泣いている'''の'''?
|Dōshite naite iru '''no''' ?
|Pourquoi pleures-tu ?
|-
|かい (de かよ (か + よ))
|何してるの'''かい'''?
|Nani shiteru no '''kai''' ?
|Qu'est-ce que tu fabriques ?
|}
<u>Remarque :</u>
# La particule '''か''' est la plus classique pour marquer l'interrogation, en particulier dans le style poli (premier exemple).
# En général le point d'interrogation est inutile à l'écrit, puisque la particule '''か''' marque sans ambigüité le caractère interrogatif de l'énoncé. Le point d'interrogation est utilisé pour indiquer l'accentuation montante.
# La particule d'interrogation '''の''' est surtout employée par les femmes et les enfants.
# Il existe de nombreuses variantes pour formuler une question, mais elles ont toutes en commun d’être ''accrochées'' à la fin de la phrase. Par exemple la forme familière en '''かい''' (''kai''), est surtout employée par les hommes à l'égard des enfants.
=== Particule d'énumération 「か」 ===
Elle correspond à la conjonction « ou ».
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|林檎'''か'''杏子をかいます。
|Ringo '''ka''' anzu wo kaimasu.
|Achetons des pommes ou des abricots.
|}
=== Particule de nominalisation 「の」 ===
'''Pronom indéfini'''
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|美味しい'''の'''を食べます。
|Oishī '''no''' wo tabemasu.
|J'en mange une de délicieuse.
|}
=== Particule de substantivation 「の」 ===
Correspond en gros « le fait de… ».
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|林檎を食べる'''の'''が好きです。
|Ringo wo taberu '''no''' ga suki desu.
|« J’aime manger des pommes. »<br />
ou, littéralement « Le fait de manger des pommes est aimé. »
|}
=== Particules à valeur modale ===
Ce sont des particules qui se placent généralement à la fin de la phrase, reflètent la classe sociale, le sexe, l'état d'esprit… du locuteur, relèvent souvent du langage familier et ne sont pas traduisibles littéralement. Il en existe de nombreuses variantes, propres à chaque dialecte et à chaque locuteur.
{|class="wikitable"
|-
!Particule
!Phrase japonaise
!Rōmaji
!Traduction
!Nuance
!Commentaires
|-
|よ
|この、林檎が食べたい'''よ'''。
|Kono, ringo ga tabetai '''yo'''.
|Moi, je veux manger cette pomme.
|forte valeur emphatique.
|tous les locuteurs
|-
|ね
|面白い、'''ね'''?
|Omoshiroi, '''ne'''?
|C'est intéressant, pas vrai ?
|accord avec l'interlocuteur
|tous les locuteurs
|-
|さ
|
|
|
|exclamation atténuée
|locuteurs masculins
|-
|わ
|残念だ'''わ'''!
|Zannenda '''wa'''!
|Dommage !
|exclamation atténuée
|utilisée par les femmes
|-
|な
|面白い'''な'''。
|Omoshiroi '''na'''.
|Tiens, c’est intéressant.
|discours intérieur
|tous les locuteurs
|-
|の
|その林檎はとても美味しい'''の'''。
|Sono ringo wa totemo oishī '''no'''.
|Cette pomme est délicieuse, tu sais.
|emphatique ou explicative
|en particulier les femmes et les enfants
|}
* <u>Remarques : </u>
# '''ぞ''' est une variante (masculine) très familière voire vulgaire de '''よ''' ;
# '''ぜ''' (de ぞい, venant de ぞよ (ぞ + よ)) est une variante familière de '''よ''' employée notamment à Tokyo.
# '''なあ''' est une variante plus pensive de '''な''' ;
# '''の''' confère à la phrase une nuance emphatique ou explicative (correspond à peu près à « ''c'est que…'' ») ; l’expression appartient au registre familier ;
# '''ね''' exprime l'accord attendu avec le locuteur, cela correspond à « ''n'est-ce pas !?'' » (ou à « hein !? » de manière familière). Il s'agit surtout d'un message phatique, destiné à vérifier que votre interlocuteur vous écoute. Utilisé par les hommes comme par les femmes. Elle peut aussi être utilisée seule :
::{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|'''ね、ね、ね'''?
|''Ne, ne, ne?''
|Hein, hein, hein ?
|}
== Résumé ==
Voilà un tableau contenant les particules les plus courantes :
{|class = "wikitable"
!Particule
!Traduction
!Fonction
|-
|は (ha (prononcé 「わ」))
|
|Indique le thème, met l'emphase sur un élément.
|-
|が (ga)
|
|Indique le sujet.
|-
|を (wo (prononcé「お」))
|
|Indique le COD.
|-
|から (kara)
|de, depuis, à partir de
|Indique le point de départ de l'action.
|-
|まで (made)
|jusqu'à
|Indique l'objectif ou la fin de l'action.
|-
|に (ni)
|à (mouvement), sur, à
|Indique la cible ou la destination de l'action. Indique le moment de l'action.
|-
|へ (he (prononcé「え」))
|à, vers
|Indique la direction de l'action.
|-
|で (de)
|en, par, avec, à
|Indique le lieu, la manière, le moyen ou le contexte de l'action.
|-
|の (no)
|de (possessif)
|Indique la relation de possession entre deux éléments.
|-
|と (to)
|et, avec
|Liste des éléments. Indique le co-acteur de l'action.
|-
|か (ka)
|ou
|Indique des choix. Indique une question.
|}
== Problèmes fréquents reliés aux particules ==
=== La variation が/は ===
Selon l'aspect du prédicat (dynamique ou non), les particules peuvent ou non marquer la thématisation de l'élément marqué. Voici les possibilités :
{|class="wikitable"
|-
!Proposition
!Phrase japonaise
!Rōmaji
!Traduction
|-
|SN-ga + Prédicat Statif <span style="font-size:1.2em">⇒</span> SN thématisé
|空'''が''' 青いです。
|Sora '''ga''' aoi desu.
|Le ciel est bleu<br />(mais pas d'habitude)
|-
|SN-ha + Prédicat Statif <span style="font-size:1.2em">⇒</span> SN non thématisé
|空'''は''' 青いです。
|Sora '''wa''' aoi desu.
|Le ciel est bleu.
|-
|SN-ga + Prédicat Dynamique <span style="font-size:1.2em">⇒</span> SN non thématisé
|ジョン'''が''' 日本に行く。
|Jon '''ga''' Nihon ni iku.
|John va au Japon.
|-
|SN-ha + Prédicat Dynamique <span style="font-size:1.2em">⇒</span> SN thématisé
|ジョン'''は''' 日本に行く。
|Jon '''wa''' Nihon ni iku.
|Quant à John, il va au Japon.
|}
=== La phrase dite « à multiples sujets » ===
Fait particulièrement étonnant en japonais, une phrase à un prédicat peut posséder plusieurs syntagmes marqués par la particule sujet. Cette construction n'est possible qu'avec des prédicats verbaux statifs (capacité, état etc.).
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
!Traduction
|-
|山'''が'''木が少ない。
|Yama '''ga''' ki ga sukunai.
|Les arbres de la montagne sont peu nombreux.
|-
|ジョン'''が'''日本語が出来ます。
|Jon '''ga''' nihongo ga dekimasu.
|John parle japonais.
|}
Dans les deux exemples ci-dessus, il serait plus naturel d’utiliser les tournures suivantes :<br />
{|class="wikitable"
|-
!Phrase japonaise
!Rōmaji
|-
|山'''は'''木が少ない。
|Yama '''wa''' ki ga sukunai.
|-
|ジョン'''は'''日本語が出来ます。
|Jon '''wa''' nihongo ga dekimasu.
|}
<u>Remarque :</u> le sens est sensiblement le même mais l’utilisation de la particule de thème (wa) est plus que conseillée. En effet, dans la première phrase, les arbres sont ceux de la montagne. On pourrait donc transcrire la phrase japonaise ci-dessus par : « '''À propos de''' la montagne, les arbres sont peu nombreux. »
=Résumé=
Voilà un tableau contenant les particules les plus courantes :
{|class = "wikitable"
!Particule
!Traduction
!Fonction
|-
|は (ha (prononcé 「わ」))
|
|Indique le thème, met l'emphase sur un élément.
|-
|が (ga)
|
|Indique le sujet.
|-
|を (wo (prononcé「お」))
|
|Indique le COD.
|-
|から (kara)
|de, depuis, à partir de
|Indique le point de départ de l'action.
|-
|まで (made)
|jusqu'à
|Indique l'objectif ou la fin de l'action.
|-
|に (ni)
|à (mouvement), sur, à
|Indique la cible ou la destination de l'action. Indique le moment de l'action.
|-
|へ (he (prononcé「え」)
|à, vers
|Indique la direction de l'action.
|-
|で (de)
|en, par, avec, à
|Indique le lieu, la manière, le moyen ou le contexte de l'action.
|-
|の (no)
|de (possessif)
|Indique la relation de possession entre deux éléments.
|-
|と (to)
|et, avec
|Liste des éléments. Indique le co-acteur de l'action.
|-
|か (ka)
|ou
|Indique des choix. Indique une question.
|}
= Voir aussi =
Article de Wikipédia sur le sujet : [[w:Particule en japonais|Particule en japonais]]
{{Grammaire Japonaise}}
[[Catégorie:Grammaire Japonaise]]
dk45hedyx0blopk0izlvidzumh7ha9h
Japonais/Vocabulaire/Animaux
0
4983
772022
746766
2026-09-11T20:15:22Z
~2026-49266-35
124543
/* Mammifères */
772022
wikitext
text/x-wiki
== Classifications ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Chaîne alimentaire
|[[wikt:fr:食物連鎖|食物連鎖]]
|しょくもつれんさ
|shokumotsu rensa
|/
|-
|Pâture
|[[wikt:fr:餌食|餌食]]
|えじき
|ejiki
|/
|-
|Prédateur
|[[wikt:fr:捕食者|捕食者]]
|ほしょくしゃ
|hoshokusha
|/
|-
|Prédation
|[[wikt:fr:捕食|捕食]]
|ほしょく
|hoshoku
|/
|-
|Proie
|[[wikt:fr:獲物|獲物]]
|えもの
|emono
|/
|}
== Ordres ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Carnivore
|[[wikt:fr:肉食動物|肉食動物]]
|にくしょくどうぶつ
|nikushoku dōbutsu
|Carnivore/Animal
|-
|Herbivore
|[[wikt:fr:草食動物|草食動物]]
|そうしょくどうぶつ
|sōshoku dōbutsu
|Herbivore/Animal
|-
|Frugivore
|[[wikt:fr:|fr:]]
|
|
|Frugivore/Animal
|-
|Granivore
|[[wikt:fr:|fr:]]
|
|
|Granivore/Animal
|-
|Insectivore
|[[wikt:fr:食虫動物|食虫動物]]
|しょくちゅうどうぶつ
|shokuchū dōbutsu
|Insectivore/Animal
|-
|Omnivore
|[[wikt:fr:雑食動物|雑食動物]]
|ざっしょくどうぶつ
|zasshoku dōbutsu
|Omnivore/Animal
|}
== Arachnides ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Acarien/Pou
|[[wikt:fr:壁蝨|壁蝨]]
|だに
|dani
|
|-
|Araignée
|[[wikt:fr:蜘蛛|蜘蛛]]
|くも
|kumo
|
|-
|Scorpion
|[[wikt:fr:蠍|蠍]]
|さそり
|sasori
|
|}
== Insectes ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Abeille
|[[wikt:fr:蜂|蜂]]
|はち
|hachi
|
|-
|Blatte, cafard
|[[wikt:fr:蜚蠊|蜚蠊]]
|ごきぶり
|gokiburi
|
|-
|Cigale
|[[wikt:fr:蝉|蝉]]
|せみ
|semi
|/Insecte
|-
|Coccinelle
|[[wikt:fr:天道虫|天道虫]]
|てんとうむし
|tentōmushi
|Sentier céleste/Insecte
|-
|Criquet
|[[wikt:飛蝗|飛蝗]]
|ばった
|batta
|/Insecte
|-
|Diamant noir
|[[wikt:fr:大鍬形|大鍬形]]
|おおくわがた
|ōkuwagata
|/
|-
|Dynaste Hercule
|[[wikt:長戟大甲蟲|長戟大甲蟲]]
|ちょうげきだいこうむし
|chōgeki daikōmushi
|/
|-
|Grillon
|[[wikt:fr:蟋蟀|蟋蟀]]
|こおろぎ
|kōrogi
|/Insecte
|-
|Fourmi
|[[wikt:fr:蟻|蟻]]
|あり
|ari
|/Insecte
|-
|Guêpe, frelon
|[[wikt:fr:雀蜂|雀蜂]]
|すずめばち
|suzumebachi
|Moineau/Abeille
|-
|
|[[wikt:fr:頬長雀蜂|頬長雀蜂]]
|ほおながすずめばち
|hōnagasuzumebachi
|Joue/Long/Guêpe
|-
|Insecte
|[[wikt:fr:虫|虫]]
|むし
|mushi
|
|-
|Libellule
|[[wikt:fr:蜻蛉|蜻蛉]]
|とんぼ
|tonbo
|/Insecte
|-
|Lucane cerf-volant
|[[wikt:fr:鍬形虫|鍬形虫]]
|くわがたむし
|Kuwagatamushi
|//
|-
|Luciole
|[[wikt:fr:蛍|蛍]]
|ほたる
|hotaru
|Petit couvercle/Insecte
|-
|Mante orchidée
|[[wikt:fr:花蟷螂|花蟷螂]]
|はなかまきり
|hanakamakiri
|Fleur/Mante religieuse
|-
|Mante religieuse
|[[wikt:fr:蟷螂|蟷螂]]
|かまきり
|kamakiri
|/
|-
|Mite
|[[wikt:fr:蛾|蛾]]
|が
|ga
|/
|-
|Mouche
|[[wikt:fr:蝿|蝿]]
|はえ
|hae
|
|-
|Moustique
|[[wikt:fr:蚊|蚊]]
|か
|ka
|
|-
|Papillon
|[[wikt:fr:蝶|蝶]]
|ちょう
|chō
|
|-
|Puce
|[[wikt:fr:蚤|蚤]]
|のみ
|nomi
|
|-
|Sauterelle
|[[wikt:蝗|蝗]]
|いなご
|inago
|/Insecte
|-
|Scarabée
|[[wikt:fr:兜虫|兜虫]]
|かぶとむし
|kabutomushi
|Casque/Insecte
|}
==Oiseaux==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Hirgana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Aigle
|[[wikt:fr:鷲|鷲]]
|わし
|washi
|Lieu grandiose/Oiseau
|-
|Aigle royal
|[[wikt:fr:犬鷲|犬鷲]]
|いぬわし
|inuwashi
|Chien/Aigle
|-
|Aigrette
|[[wikt:fr:白鷺|白鷺]]
|しらさぎ
|shirasagi
|Blanc/Héron
|-
|Aigrette sacrée
|[[wikt:fr:黒鷺|黒鷺]]
|くろさぎ
|kurosagi
|Noir/Héron
|-
|Alouette
|[[wikt:fr:雲雀|雲雀]]
|ひばり
|hibari
|Nuage/Moineau
|-
|Autruche
|[[wikt:fr:鴕鳥|鴕鳥]]
|だちょう
|dachō
|Autruche/Oiseau
|-
|Bécasse
|[[wikt:fr:山鴫|山鴫]]
|やましぎ
|yamashigi
|Montagne/Chevalier
|-
|Bihoreau goisagi
|[[wikt:fr:溝五位|溝五位]]
|みぞごい
|mizogoi
|Fossé/Cinq/Couronne
|-
|Bihoreau violacé
|[[wikt:fr:蓑五位|蓑五位]]
|みのごい
|minogoi
|Manteau de paille/Cinq/Couronne
|-
|Caille
|[[wikt:fr:鶉|鶉]]
|うずら
|uzura
|/Oiseau
|-
|Calao
|[[wikt:fr:犀鳥|犀鳥]]
|さいちょう
|saichō
|Rhinocéros/Oiseau
|-
|Calopsitte
|[[wikt:阿亀鸚哥|阿亀鸚哥]]
|おかめいんこ
|okame inko
|Femme inélégante/Perruche
|-
|Canard
|[[wikt:fr:鴨|鴨]] (sauvage)<br />[[wikt:fr:家鴨|家鴨]] (domestique)
|かも<br />あひる
|kamo<br />ahiru
|Enveloppe dure/Oiseau<br />Maison/Canard sauvage
|-
|Canard colvert
|[[wikt:fr:真鴨|真鴨]]
|まがも
|magamo
|Vrai/Canard sauvage
|-
|Canard mandarin
|[[wikt:fr:鴛鴦|鴛鴦]]
|おしどり
|oshidori
|/
|-
|Canaroie semipalmée
|[[wikt:fr:鵲雁|鵲雁]]
|かささぎがん
|kasasagigan
|Pie/Oie sauvage
|-
|Casoar
|[[wikt:fr:火食鳥|火食鳥]]
|ひくいどり
|hikuidori
|Feu/Mangeant/Oiseau
|-
|Cassican flûteur
|[[wikt:fr:鵲笛鴉|鵲笛鴉]]
|かささぎふえがらす
|kasasagi fuegarasu
|Pie/Flûte/Corbeau
|-
|Chouette
|[[wikt:fr:梟|梟]]
|ふくろう
|fukurō
|Oiseau/Arbre
|-
|Cigogne
|[[wikt:fr:鸛|鸛]]
|こうのとり
|kōnotori
|Enfant/Oiseau
|-
|Cigogne blanche
|[[wikt:fr:朱嘴鸛|朱嘴鸛]]
|しゅばしこう
|shubashikō
|Vermillon/Bec/Cigogne
|-
|Cigogne d'Abdim
|[[wikt:fr:青端鸛|青端鸛]]
|あおはしこう
|aohashikō
|Bleu/Extrémité/Cigogne
|-
|Cigogne épiscopale
|[[wikt:fr:白襟鸛|白襟鸛]]
|しろえりこう
|shiroerikō
|Blanc/Cou/Cigogne
|-
|Cigogne maguari
|[[wikt:fr:燕尾鸛|燕尾鸛]]
|えんびこう
|enbikō
|Hirondelle/Queue/Cigogne
|-
|Cigogne noire
|[[wikt:fr:鍋鸛|鍋鸛]]
|なべこう
|nabekō
|Chaudron/Cigogne
|-
|Colombe/Pigeon
|[[wikt:fr:鳩|鳩]]
|はと
|hato
|/Oiseau
|-
|Condor
|[[wikt:fr:公佗児|公佗児]]
|こうわじ
|kōwaji
|Public/Fier ; solitaire/ (On trouve également le terme 「コンドル」issu de l'anglais "condor".)
|-
|Coq de bruyère
|[[wikt:fr:雷鳥|雷鳥]]
|らいちょう
|raichō
|Tonnerre/Oiseau
|-
|Coq
|[[wikt:fr:雄鳥|雄鳥]]
|おんどり
|ondori
|Mâle/Oiseau
|-
|Corbeau
|[[wikt:fr:烏|烏]]
|からす
|karasu
|Terme général désignant les animaux du genre ''corvus''
|-
|Corneille mantelée
|[[wikt:fr:頭巾烏|頭巾烏]]
|ずきんがらす
|zukingarasu
|Capuche/Corbeau
|-
|Dinde
|[[wikt:fr:七面鳥|七面鳥]]
|しちめんちょう
|shichimenchō
|Sept visages/Oiseau
|-
|Faisan
|[[wikt:fr:雉|雉]]
|きじ
|kiji
|
|-
|Faucon
|[[wikt:fr:鷹|鷹]]
|たか
|taka
|
|-
|Faucon crécerelle
|[[wikt:fr:長元坊|長元坊]]
|ちょうげんぼう
|chōgenbō
|Chef/Fondation/Chambre
|-
|Faucon pèlerin
|[[wikt:fr:隼|隼]]
|はやぶさ
|hayabusa
|
|-
|Geai
|[[wikt:fr:懸巣|懸巣]]
|かけす
|kakesu
|/
|-
|Grand-duc de Blakiston
|[[wikt:fr:島梟|島梟]]
|しまふくろう
|shimafukurō
|Île/Chouette
|-
|Grèbe castagneux
|[[wikt:fr:鸊鷉|鸊鷉]]
|かいつぶり
|kaitsuburi
|/
|-
|Grive
|[[wikt:fr:鶇|鶇]]
|つぐみ
|tsugumi
|Tenir sa langue/Oiseau
|-
|Grue demoiselle
|[[wikt:fr:姉羽鶴|姉羽鶴]]
|あねはづる
|anehazuru
|Grande sœur/Aile/Grue
|-
|Grue
|[[wikt:fr:鶴|鶴]]
|つる
|tsuru
|S'élever/Oiseau
|-
|Grue royale
|[[wikt:fr:頬白冠鶴|頬白冠鶴]]
|ほおじろかんむりづる
|hōjiro kanmurizuru
|Bruant à longue queue/Grue couronnée
|-
|Harpie féroce
|[[wikt:fr:扇鷲|扇鷲]]
|おうぎわし
|ōgiwashi
|Éventail/Aigle
|-
|Héron cendré
|[[wikt:fr:蒼鷺|蒼鷺]]
|あおさぎ
|aosagi
|Bleu/Héron
|-
|Hibou
|[[wikt:fr:木菟|木菟]]
|みみずく
|mimizuku
|Arbre/Lapin
|-
|Hirondelle
|[[wikt:fr:燕|燕]]
|つばめ
|tsubame
|
|-
|Hirondelle bicolore
|[[wikt:fr:緑燕|緑燕]]
|みどりつばめ
|midori tsubame
|Vert/Hirondelle
|-
|Huppe fasciée
|[[wikt:fr:戴勝|戴勝]]
|やつがしら
|yatsugashira
|Serviteur/Tête
|-
|Lagopède alpin
|[[wikt:fr:雷鳥|雷鳥]]
|らいちょう
|raichō
|Foudre/Oiseau
|-
|Macareux moine
|[[wikt:fr:西角目鳥|西角目鳥]]
|にしつのめどり
|nishitsunomedori
|Ouest/Macareux cornu
|-
|Martinet
|[[wikt:fr:雨燕|雨燕]]
|あまつばめ
|amatsubame
|Pluie/Hirondelle
|-
|Moineau
|[[wikt:fr:雀|雀]]
|すずめ
|suzume
|
|-
|Mouette
|[[wikt:fr:鴎|鴎]]
|かもめ
|kamome
|Arrondissement citadin/Oiseau
|-
|Oie
|[[wikt:fr:鵞鳥|鵞鳥]]
|がちょう
|gachō
|Oie/Oiseau
|-
|Oiseau de proie
|[[wikt:fr:猛禽|猛禽]]
|もうきん
|mōkin
|Fureur/Oiseau
|-
|Oiseau
|[[wikt:fr:鳥|鳥]]
|とり
|tori
|
|-
|Palombe/Ramier
|[[wikt:fr:森鳩|森鳩]]
|もりばと
|moribato
|Forêt/Pigeon
|-
|Paon
|[[wikt:fr:孔雀|孔雀]]
|くじゃく
|kujaku
|Cavité/Moineau
|-
|Paradisier
|[[wikt:fr:風鳥|風鳥]]
|ふうちょう
|fūchō
|Vent/Oiseau
|-
|Perroquet
|[[wikt:fr:鸚鵡|鸚鵡]]
|おうむ
|ōmu
|
|-
|Perruche
|[[wikt:fr:鸚哥|鸚哥]]
|いんこ
|inko
|
|-
|Pie
|[[wikt:fr:鵲|鵲]]
|かささぎ
|kasasagi
|Autrefois/Oiseau
|-
|Plongeon imbrin
|[[wikt:fr:嘴黒阿比|嘴黒阿比]]
|はしぐろあび
|hashiguroabi
|Bec noir/Plongeon
|-
|Poule
|[[wikt:fr:雌鶏|雌鶏]]
|めんどり
|mendori
|Femelle/Poulet
|-
|Poulet
|[[wikt:fr:鶏|鶏]] (domestique)
|にわとり
|niwatori
|Jardin/Oiseau
|-
|Passerin indigo
|[[wikt:fr:瑠璃野路子|瑠璃野路子]]
|るりのじこ
|rurinojiko
|/Enfant
|-
|Roitelet
|[[wikt:fr:鷦鷯|鷦鷯]]
|みそさざい
|misosazai
|/
|-
|Roselin pourpré
|[[wikt:fr:紫猿子|紫猿子]]
|むらさきましこ
|murasaki mashiko
|Violet/Singe/Enfant
|-
|Rossignol
|[[wikt:fr:小夜啼鳥|小夜啼鳥]]
|さよなきどり
|sayonakidori
|Petite nuit/Oiseau chanteur
|-
|Rouge-gorge
|[[wikt:fr:駒鳥|駒鳥]]
|こまどり
|komadori
|Chevalet/Oiseau
|-
|Sarcelle
|[[wikt:fr:小鴨|小鴨]]
|こがも
|kogamo
|Petit/Canard sauvage
|-
|Sterne arctique
|[[wikt:fr:極鰺刺|極鰺刺]]
|きょくあじさし
|kyokuajisashi
|Extrême/Sterne
|-
|Sterne
|[[wikt:fr:鰺刺|鰺刺]]
|あじさし
|ajisashi
|Chinchard/
|-
|Toucan
|[[wikt:fr:大嘴|大嘴]]
|おおはし
|ōhashi
|Grand/Bec
|-
|Tourterelle
|[[wikt:fr:山鳩|山鳩]]
|やまばと
|yamabato
|Montagne/Colombe, pigeon
|-
|Tourterelle des bois
|[[wikt:fr:小雉鳩|小雉鳩]]
|こきじばと
|kokijibato
|Petit/Tourterelle occidentale
|-
|Vautour
|[[wikt:fr:禿鷹|禿鷹]]
|はげたか
|hagetaka
|Calvitie/Faucon
|}
== Mammifères ==
[[Image:Books-aj.svg aj ashton 01.svg|right|70px]]
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Agneau
|[[wikt:fr:子羊|子羊]]
|こひつじ
|kohitsuji
|Enfant/Mouton
|-
|Alpaga
|
|アルパカ
|arupaka
|
|-
|Âne
|[[wikt:fr:驢馬|驢馬]]
|ろば
|roba
|Ce terme s'écrit en katakana comme suit :「ロバ」
|-
|Babouin
|[[wikt:狒々|狒々]]
|ひひ
|hihi
|Issu de la créature légendaire du folklore japonais : ''[[w:Hi-hi|Hi-hi]]''
|-
|Baleine
|[[wikt:fr:鯨|鯨]]
|くじら
|kujira
|Ce terme s'écrit en katakana comme suit :「クジラ」
|-
|Bassaris rusé
|
|カコミスル
|kakomisuru
|Issu de l'anglais « cacomistle ».
|-
|Belette d’Europe
|[[wikt:fr:飯綱|飯綱]]
|いいづな
|īzuna
|Riz/corde. Désignait originellement une créature légendaire du nom de [[w:Kuda-kitsune|kuda-gitsune]].
|-
|Belette du Japon / Itatsi
|㹨
|いたち
|Itachi
|Désigne également les mustélidés en général. Ce terme s’écrit en katakana comme suit : 「イタチ」
|-
|Bélier
|[[wikt:雄羊|雄羊]]
|おひつじ
|ohitsuji
|Mâle/Mouton
|-
|Bison
|[[wikt:野牛|野牛]]
|やぎゅう
|yagyū
|Champ/Bœuf. Est surtout désigné sous le terme baizon 「バイゾン」
|-
|Blaireau
|[[wikt:fr:穴熊|穴熊]]
|あなぐま
|anaguma
|Trou (穴) Ours (熊). Ce terme s'écrit en katakana comme suit :「アナグマ」
|-
|Blaireau japonais
|[[wikt:fr:日本穴熊|日本穴熊]]
|にほんあなぐま
|Nihon anaguma
|Japon (日本) Blaireau (穴熊)
|-
|Bouc
|[[wikt:雄山羊|雄山羊]]
|おやぎ
|oyagi
|Mâle/Chèvre
|-
|Brebis
|[[wikt:雌羊|雌羊]]
|めひつじ
|mehitsuji
|Femelle/Mouton
|-
|Buffle
|水牛
|すいぎゅ
|suigyu
|Eau/Bœuf : Vient du buffle d’Eau d’Asie.
|-
|Castor
|
|ビーバー
|bībā
|Beaver (en)
|-
|Chameau
|[[wikt:fr:駱駝|駱駝]]
|らくだ
|rakuda
|
|-
|Chat
|[[wikt:fr:猫|猫]]
|ねこ
|neko
|
|-
|Chaton
|[[wikt:fr:子猫|子猫]]
|こねこ
|koneko
|Enfant/Chat
|-
|Chauve-souris
|[[wikt:fr:蝙蝠|蝙蝠]]
|こうもり
|kōmori
|
|-
|Chevreau
|[[wikt:子山羊|子山羊]]
|こやぎ
|koyagi
|Enfant/Chèvre
|-
|Chevreuil
|[[wikt:麕鹿|麕鹿]]
|のろじか
|norojika
|Chevreuil/Cerf
|-
|Cerf
|[[wikt:fr:鹿|鹿]]
|しか
|shika
|
|-
|Cerf du père David
|四不像
|ししふうぞう
|shifuzō
|Quatre/Négatif/Image (en tête). Du chinois « aucun des quatre »
|-
|Cerf sika
|日本鹿
|にほんじか
|Nihon jika
|Japon/Cerf
|-
|Chacal
|
|ジャッカル
|jakkaru
|jackal (en)
|-
|Cheval
|[[wikt:fr:馬|馬]]
|うま
|uma
|
|-
|Chèvre
|[[wikt:fr:山羊|山羊]]
|やぎ
|yagi
|Montagne (山) Mouton (羊)
|-
|Chien
|[[wikt:fr:犬|犬]]
|いぬ
|inu
|
|-
|Chien viverrin / Tanuki
|[[wikt:fr:狸|狸]]
|たぬき
|tanuki
|
|-
|Chinchilla
|
|チンチラ
|chinchira
|
|-
|Chiot
|[[wikt:fr:子犬|子犬]]
|こいぬ
|koinu
|Enfant (子) Chien (犬)
|-
|Civette
|[[wikt:fr:麝香猫|麝香猫]]
|じゃこうねこ
|jakōneko
|Musc (麝香) Chat (猫)
|-
|Coati
|鼻熊
|はなぐま
|hanaguma
|Nez (鼻) Ours (熊)
|-
|Cochon
|[[wikt:fr:豚|豚]]
|ぶた
|buta
|
|-
|Cochon d'Inde
|
|モルモット
|morumotto
|On trouve également le terme 「ギニアピッグ」
|-
|Coyote
|
|コヨーテ
|koyōte
|
|-
|Daim
|駝鹿
|ダマジカ
|damajika
|Daim/Cerf
|-
|Dasyure
|[[wikt:fr:袋猫|袋猫]]
|ふくろねこ
|fukuroneko
|Sac (袋) Chat (猫)
|-
|Dauphin
|[[wikt:fr:海豚|海豚]]
|いるか
|iruka
|Mer/Cochon
|-
|Dègue
|
|デグー
|degū
|
|-
|Dhole
|犲
|やまいぬ
|Yamainu
|Montagne/Chien. Confondu avec les chiens errants et les loups, est aujourd’hui plutôt désigné sous le nom de ''dōru'' 「ドール」
|-
|Dromadaire
|[[wikt:fr:一瘤駱駝|一瘤駱駝]]
|ひとこぶらくだ
|hitokobu rakuda
|Bosse unique/Chameau
|-
|Dugong / Vache marine
|儒艮
|じゅごん
|jugon
|
|-
|Échidné
|[[wikt:fr:針土竜|針土竜]]
|はりねずみ
|harimogura
|Aiguille/Taupe
|-
|Écureuil
|[[wikt:fr:栗鼠|栗鼠]]
|りす
|risu
|Noix (栗) Rat (鼠)
|-
|Écureuil volant
|[[wikt:fr:鼯鼠|鼯鼠]]
|ももんが
|momonga
|Écureuil volant (鼯) Rat (鼠)
|-
|Élan
|[[wikt:fr:箆鹿|箆鹿]]
|へらじか
|herajika
|Spatule (箆) Cerf (鹿)
|-
|Éléphant
|[[wikt:fr:象|象]]
|ぞう
|zō
|-
|Faon
|[[wikt:子鹿|子鹿]]
|こじか
|kojika
|Enfant/Cerf
|-
|Fouine
|[[wikt:fr:胸白貂|胸白貂]]
|むねしろてん
|muneshiroten
|Poitrail blanc/Martre
|-
|Furet
|[[wikt:fr:白鼬|白鼬]]
|しろいたち
|shiroitachi
|Blanc/Belette (On trouve également le terme「フェレット」issu de l'anglais "ferret".)
|-
|Gerbille de Mongolie
|[[wikt:fr:砂鼠|砂鼠]]
|すなねずみ
|sunanezumi
|Sable (砂) Souris (鼠)
|-
|Gibbon
|[[wikt:fr:手長猿|手長猿]]
|てながざる
|tenagazaru
|Main/Long/Singe
|-
|Gibbon à mains blanches
|[[wikt:fr:白手手長猿|白手手長猿]]
|しろててながざる
|shirote tenagazaru
|Blanc/Main/Long/Singe
|-
|Girafe
|[[wikt:fr:麒麟|麒麟]]
|きりん
|kirin
|Issue de la créature légendaire du folklore assiatique : [[w:Qilin|Qilin]]
|-
|Glouton
|[[wikt:fr:屈狸|屈狸]]
|くずり
|kuzuri
|Ce terme s'écrit en katakana comme suit:「クズリ」
|-
|Gorille
|
|ゴリラ
|gorira
|Gorilla (en)
|-
|Grizzly
|[[wikt:fr:灰色熊|灰色熊]]
|はいいろぐま
|haīroguma
|Gris/Ours
|-
|Guépard
|[[wikt:fr:狩猟豹|狩猟豹]]
|しゅりょうひょう
|shuryōhyō
|Chasse/Panthère (On trouve également le terme 「チーター」issu de l'anglais "cheetah".)
|-
|Hamster
|
|ハムスター
|hamusutā
|Hamster (fr)
|-
|Hermine
|[[wikt:fr:白鼬|白鼬]]
|おこじょ
|okojo
|Blanc/Belette
|-
|Hérisson
|[[wikt:fr:針鼠|針鼠]]
|はりねずみ
|harinezumi
|Aiguille (針) Rat (鼠)
|-
|Hippopotame
|[[wikt:fr:河馬|河馬]]
|かば
|kaba
|Fleuve (河) Cheval (馬)
|-
|Kinkajou
|
|キンカジュー
|kinkajū
|Variante de quincajou.
|-
|Lama
|
|ラマ
|rama
|Lama (fr)
|-
|Lamantin
|
|マナティー
|manatī
|Issu de l'anglais "manatee".
|-
|Lapin
|[[wikt:fr:兎|兎]]
|うさぎ
|usagi
|Terme général désignant les lagomorphes
|-
|Léopard/Panthère
|[[wikt:豹#ja|豹]]
|ひょう
|hyō
|
|-
|Lièvre
|[[wikt:fr:野兎|野兎]]
|のうさぎ
|nousagi
|Champ (野) Lapin (兎)
|-
|Lion
|[[wikt:fr:獅子|獅子]]
|しし
|shishi
|Le terme 「ライオン」 issu de l'anglais "lion" est aujourd'hui plus largement utilisé.
|-
|Loup
|[[wikt:fr:狼|狼]]
|おおかみ
|ōkami
|
|-
|Loutre
|[[wikt:fr:川獺|川獺]]
|かわうそ
|kawauso
|Rivère (川) Loutre (獺)
|-
|Loutre de mer
|海獺
|らっこ
|rakko
|Mer/Loutre
|-
|Loutre japonaise
|[[wikt:fr:日本川獺|日本川獺]]
|にほんあなぐま
|Nihon kawauso
|Japon (日本) Loutre (獺) (Cette espèce est aujourd’hui éteinte.)
|-
|Lycaon
|
|リカオン
|rikaon
|
|-
|Lynx
|[[wikt:fr:大山猫|大山猫]]
|おおやまねこ
|ōyamaneko
|Grand (大) Montagne (山) Chat (猫)
|-
|Marmotte
|
|ウッドチャック
|uddochakku
|Issu de l'anglais "woodchuck".
|-
|Martre
|貂
|てん
|ten
|
|-
|Mouffette
|
|スカンク
|sukanku
|Skunk (en)
|-
|Mouton
|[[wikt:fr:羊|羊]]
|ひつじ
|hitsuji
|
|-
|Mule
|[[wikt:fr:騾馬|騾馬]]
|らば
|raba
|Cheval (馬)
|-
|Mulet
|[[wikt:fr:駃騠|駃騠]]
|けってい
|kettei
|
|-
|Narval
|[[wikt:一角|一角]]
|いっかく
|ikkaku
|Une (一), Corne (角)
|-
|Numbat
|[[wikt:袋蟻食|袋蟻食]]
|ふくろありくい
|fukuroarikui
|Poche/Fourmilier
|-
|Opossum
|
|[[wikt:オポッサム|オポッサム]]
|opossamu
|Opossum (en)
|-
|Ornithorynque
|[[wikt:fr:鴨嘴|鴨嘴]]
|かものはし
|kamonohashi
|Canard/Bec
|-
|Ours
|[[wikt:fr:熊|熊]]
|くま
|kuma
|
|-
|Ours brun
|羆
|ひぐま
|higuma
|
|-
|Ours noir d’Asie
|月輪熊
|ツキノワグマ
|tsukinowaguma
|Lune/Anneau/Ours. Est parfois raccourci par ''tsukiguma'' (月熊)
|-
|Ours polaire
|[[wikt:fr:白熊|白熊]]
|しろくま
|shirokuma
|Blanc (白) Ours (熊)
|-
|Panda
|
|パンダ
|panda
|Panda (fr)
|-
|Paresseux
|[[wikt:樹懶|樹懶]]
|なまけもの
|namakemono
|
|-
|Petit panda
|
|レッサーパンダ
|ressā panda
|Issu de l'anglais "lesser panda".
|-
|Porc-épic
|[[wikt:fr:山荒|山荒]]
|やまあらし
|yamaarashi
|Montagne (山) Stérile (荒)
|-
|Protèle
|[[wikt:fr:土狼|土狼]]
|つちおおかみ
|tsuchiōkami
|Terre/Loup (On trouve également le terme 「アードウルフ」issu de l'anglais "aardwolf".)
|-
|Puma
|
|ピューマ
|pyūma
|Puma (en)
|-
|Putois
|[[wikt:fr:毛長鼬|毛長鼬]]
|けながいたち
|kenagaitachi
|Poil long/Belette
|-
|Rat
|[[wikt:fr:熊鼠|熊鼠]]
|くまねずみ
|kumanezumi
|Ours/Souris
|-
|Raton laveur
|[[wikt:fr:洗熊|洗熊]]
|あらいぐま
|araiguma
|Laver/Ours
|-
|Renard
|[[wikt:fr:狐|狐]]
|きつね
|kitsune
|
|-
|Renard argenté
|[[wikt:fr:銀狐|銀狐]]
|ぎんぎつね
|gingitsune
|Argent (銀) Renard (狐)
|-
|Renard gris
|[[wikt:fr:灰色狐|灰色狐]]
|はいいろぎんぎつね
|haīro gitsune
|Cendre (灰) Couleur (色) Renard (狐)
|-
|Rhinoceros
|[[wikt:fr:犀|犀]]
|さい
|sai
|
|-
|Sanglier
|[[wikt:fr:猪|猪]]
|いのしし
|inoshishi
|
|-
|Saro
|鴨鹿
|カモシカ
|kamoshika
|Canard sauvage/Cerf. Les kanjis ne sont jamais utilisés, le nom est écrit exclusivement en katakana
|-
|Siamang
|[[wikt:fr:袋手長猿|袋手長猿]]
|ふくろてながざる
|fukurotenagazaru
|Sac/Main/Long/Singe
|-
|Singe
|[[wikt:fr:猿|猿]]
|さる
|saru
|
|-
|Souris
|[[wikt:fr:鼠|鼠]]
|ねずみ
|nezumi
|
|-
|Suricate
|
|ミーアキャット
|mīakyatto
|Meerkat (en)
|-
|Tamia
|[[wikt:fr:縞栗鼠|縞栗鼠]]
|しまりす
|shimarisu
|Rayure/Écureuil
|-
|Tapir
|[[wikt:fr:獏|獏]]
|ばく
|baku
|Issu de la créature légendaire : [[w:Baku|baku]], elle même issue des textes chinois sur le tapir.
|-
|Taupe
|[[wikt:fr:土竜|土竜]]
|もぐら
|mogura
|Terre/Dragon
|-
|Taureau
|[[wikt:fr:雄牛|雄牛]]
|おうし
|oushi
|Mâle (雄) Vache (牛)
|-
|Tigre
|[[wikt:fr:虎|虎]]
|とら
|tora
|
|-
|Vache
|[[wikt:fr:牛|牛]]
|うし
|ushi
|
|-
|Veau
|[[wikt:fr:子牛|子牛]]
|こうし
|koushi
|Enfant (子) Vache (牛)
|-
|Vison
|
|ミンク
|minku
|mink (en)
|-
|Zèbre
|[[wikt:fr:縞馬|縞馬]]
|しまうま
|shimauma
|Rayure (縞) Cheval (馬)
|-
|Zèbre de montagne
|[[wikt:fr:山縞馬|山縞馬]]
|やましまうま
|yamashimauma
|Montagne (山) Zèbre (縞馬)
|-
|Zibeline
|[[wikt:fr:黒貂|黒貂]]
|くろてん
|kuroten
|Noir (黒) Martre (貂)
|}
== Reptiles, poissons et autres vies marines ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Anguille
|[[wikt:fr:鰻|鰻]]
|うなぎ
|unagi
|
|-
|Barracuda
|[[wikt:fr:梭子魚|梭子魚]]
|かます
|kamasu
|Navette/Enfant/Poisson
|-
|Calamar
|[[wikt:fr:墨魚|墨魚]]
|いか
|ika
|Encre/Poisson
|-
|Carpe
|[[wikt:fr:鯉|鯉]]
|こい
|koi
|
|-
|Concombre de mer
|[[wikt:海鼠|海鼠]]
|なまこ
|namako
|Mer/Rat
|-
|Congre
|[[wikt:fr:穴子|穴子]]
|あなご
|anago
|Trou/Enfant
|-
|Crabe
|[[wikt:fr:蟹|蟹]]
|かに
|kani
|
|-
|Crabe de cocotier
|[[wikt:fr:椰子蟹|椰子蟹]]
|やしがに
|yashigani
|Palmier/Crabe
|-
|Crapaud
|[[wikt:蟇蛙|蟇蛙]]
|ひきがえる
|hikigaeru
|Crapaud/Grenouille
|-
|Crevette
|[[wikt:fr:蝦|蝦]]
|えび
|ebi
|
|-
|Crocodile
|[[wikt:fr:鰐|鰐]]
|わに
|wani
|
|-
|Dragon
|[[wikt:fr:竜|竜]]
|りゅう
|ryū
|Pour parler d'un dragon occidental, on emploiera「ドラゴン」.
|-
|Dragon de Komodo
|
|コモドドラゴン
|Komodo-doragon
|Komodo Dragon (en)
|-
|Escargot
|[[wikt:fr:蝸牛|蝸牛]]
|かたつむり
|katatsumuri
|Escargot/Vache
|-
|Gecko
|[[wikt:守宮|守宮]]
|やもり
|yamori
|Protection/Palais
|-
|Gecko léopard
|[[wikt:豹紋蜥蜴擬き|豹紋蜥蜴擬き]]
|ひょうもんとかげもどき
|hyōmon'tokagemodoki
|/Lézard/
|-
|Grenouille
|[[wikt:fr:蛙|蛙]]
|かえる
|kaeru
|
|-
|Iguane
|
|イグアナ
|iguana
|Iguana(en)
|-
|Léopard de mer
|[[wikt:fr:豹海豹|豹海豹]]
|ひょうあざらし
|hyōazarashi
|Panthère/Phoque
|-
|Méduse
|[[wikt:fr:水母|水母]]/[[wikt:fr:海月|海月]]
|くらげ
|kurage
|Eau/Mère ; Mer/Lune
|-
|Morse
|[[wikt:fr:海象|海象]]
|せいうち
|seiuchi
|Mer/Éléphant
|-
|Murène
|[[wikt:鱓|鱓]]
|うつぼ
|utsubo
|/
|-
|Orque
|[[wikt:fr:鯱|鯱]]
|しゃち
|shachi
|/
|-
|Otarie
|[[wikt:fr:海驢|海驢]]
|あしか
|ashika
|Mer/Âne
|-
|Phoque
|[[wikt:fr:海豹|海豹]]
|あざらし
|azarashi
|Mer/Panthère
|-
|Pieuvre
|[[wikt:fr:蛸|蛸]]
|たこ
|tako
|
|-
|Poisson
|[[wikt:fr:魚|魚]]
|さかな
|sakana
|
|-
|Poisson rouge
|[[wikt:fr:金魚|金魚]]
|きんぎょ
|kingyo
|Or/Poisson
|-
|Raie
|[[wikt:fr:鱝|鱝]]
|えい
|ei
|Poisson/Orné
|-
|Requin
|[[wikt:fr:鮫|鮫]]
|さめ
|same
|Poisson/Croisement
|-
|Requin-baleine
|甚兵衛鮫
|じんべいざめ
|jinbeizame
|Jinbei/Requin
|-
|Requin-renard
|[[wikt:fr:尾長鮫|尾長鮫]]
|おながざめ
|onagazame
|Longue queue/Requin
|-
|Requin-tigre
|[[wikt:fr:鼬鮫|鼬鮫]]
|いたちざめ
|itachizame
|Belette/Requin
|-
|Salamandre
|[[wikt:fr:蠑螈|蠑螈]]
|いもり
|imori
|/
|-
|Sardine
|[[wikt:鰯|鰯]]
|いわし
|iwashi
|Poisson/Faible
|-
|Serpent
|[[wikt:fr:蛇|蛇]]
|へび
|hebi
|
|-
|Thon
|[[wikt:fr:鮪|鮪]]
|まぐろ
|maguro
|Poisson/Bleu
|-
|Tortue
|[[wikt:fr:亀|亀]]
|かめ
|kame
|
|-
|Tortue de mer
|[[wikt:fr:海亀|海亀]]
|うみがめ
|umigame
|Mer/Tortue
|}
== Voir aussi ==
*Une série d'[[Japonais/Exercices de vocabulaire#Animaux|exercices]] sur les animaux est également disponible.
*[[wikt:fr:Catégorie:Noms_communs_japonais|Les noms dans le wiktionnaire]]
{{Glossaires_de_Japonais}}
[[Catégorie:Glossaires de Japonais]]
[[en:Japanese/Vocabulary/Animals]]
[[es:Japonés/Vocabulario/Animales]]
3pxocpmbymlv2rsdp31niiyx1ykc23m
772025
772022
2026-09-11T20:22:26Z
~2026-49266-35
124543
772025
wikitext
text/x-wiki
== Classifications ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Chaîne alimentaire
|[[wikt:fr:食物連鎖|食物連鎖]]
|しょくもつれんさ
|shokumotsu rensa
|/
|-
|Pâture
|[[wikt:fr:餌食|餌食]]
|えじき
|ejiki
|/
|-
|Prédateur
|[[wikt:fr:捕食者|捕食者]]
|ほしょくしゃ
|hoshokusha
|/
|-
|Prédation
|[[wikt:fr:捕食|捕食]]
|ほしょく
|hoshoku
|/
|-
|Proie
|[[wikt:fr:獲物|獲物]]
|えもの
|emono
|/
|}
== Ordres ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Carnivore
|[[wikt:fr:肉食動物|肉食動物]]
|にくしょくどうぶつ
|nikushoku dōbutsu
|Carnivore/Animal
|-
|Herbivore
|[[wikt:fr:草食動物|草食動物]]
|そうしょくどうぶつ
|sōshoku dōbutsu
|Herbivore/Animal
|-
|Frugivore
|[[wikt:fr:|fr:]]
|
|
|Frugivore/Animal
|-
|Granivore
|[[wikt:fr:|fr:]]
|
|
|Granivore/Animal
|-
|Insectivore
|[[wikt:fr:食虫動物|食虫動物]]
|しょくちゅうどうぶつ
|shokuchū dōbutsu
|Insectivore/Animal
|-
|Omnivore
|[[wikt:fr:雑食動物|雑食動物]]
|ざっしょくどうぶつ
|zasshoku dōbutsu
|Omnivore/Animal
|}
== Arachnides ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Acarien/Pou
|[[wikt:fr:壁蝨|壁蝨]]
|だに
|dani
|
|-
|Araignée
|[[wikt:fr:蜘蛛|蜘蛛]]
|くも
|kumo
|
|-
|Scorpion
|[[wikt:fr:蠍|蠍]]
|さそり
|sasori
|
|}
== Insectes ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Abeille
|[[wikt:fr:蜂|蜂]]
|はち
|hachi
|
|-
|Blatte, cafard
|[[wikt:fr:蜚蠊|蜚蠊]]
|ごきぶり
|gokiburi
|
|-
|Cigale
|[[wikt:fr:蝉|蝉]]
|せみ
|semi
|/Insecte
|-
|Coccinelle
|[[wikt:fr:天道虫|天道虫]]
|てんとうむし
|tentōmushi
|Sentier céleste/Insecte
|-
|Criquet
|[[wikt:飛蝗|飛蝗]]
|ばった
|batta
|/Insecte
|-
|Diamant noir
|[[wikt:fr:大鍬形|大鍬形]]
|おおくわがた
|ōkuwagata
|/
|-
|Dynaste Hercule
|[[wikt:長戟大甲蟲|長戟大甲蟲]]
|ちょうげきだいこうむし
|chōgeki daikōmushi
|/
|-
|Grillon
|[[wikt:fr:蟋蟀|蟋蟀]]
|こおろぎ
|kōrogi
|/Insecte
|-
|Fourmi
|[[wikt:fr:蟻|蟻]]
|あり
|ari
|/Insecte
|-
|Guêpe, frelon
|[[wikt:fr:雀蜂|雀蜂]]
|すずめばち
|suzumebachi
|Moineau/Abeille
|-
|
|[[wikt:fr:頬長雀蜂|頬長雀蜂]]
|ほおながすずめばち
|hōnagasuzumebachi
|Joue/Long/Guêpe
|-
|Insecte
|[[wikt:fr:虫|虫]]
|むし
|mushi
|
|-
|Libellule
|[[wikt:fr:蜻蛉|蜻蛉]]
|とんぼ
|tonbo
|/Insecte
|-
|Lucane cerf-volant
|[[wikt:fr:鍬形虫|鍬形虫]]
|くわがたむし
|Kuwagatamushi
|//
|-
|Luciole
|[[wikt:fr:蛍|蛍]]
|ほたる
|hotaru
|Petit couvercle/Insecte
|-
|Mante orchidée
|[[wikt:fr:花蟷螂|花蟷螂]]
|はなかまきり
|hanakamakiri
|Fleur/Mante religieuse
|-
|Mante religieuse
|[[wikt:fr:蟷螂|蟷螂]]
|かまきり
|kamakiri
|/
|-
|Mite
|[[wikt:fr:蛾|蛾]]
|が
|ga
|/
|-
|Mouche
|[[wikt:fr:蝿|蝿]]
|はえ
|hae
|
|-
|Moustique
|[[wikt:fr:蚊|蚊]]
|か
|ka
|
|-
|Papillon
|[[wikt:fr:蝶|蝶]]
|ちょう
|chō
|
|-
|Puce
|[[wikt:fr:蚤|蚤]]
|のみ
|nomi
|
|-
|Sauterelle
|[[wikt:蝗|蝗]]
|いなご
|inago
|/Insecte
|-
|Scarabée
|[[wikt:fr:兜虫|兜虫]]
|かぶとむし
|kabutomushi
|Casque/Insecte
|}
==Oiseaux==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Hirgana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Aigle
|[[wikt:fr:鷲|鷲]]
|わし
|washi
|Lieu grandiose/Oiseau
|-
|Aigle royal
|[[wikt:fr:犬鷲|犬鷲]]
|いぬわし
|inuwashi
|Chien/Aigle
|-
|Aigrette
|[[wikt:fr:白鷺|白鷺]]
|しらさぎ
|shirasagi
|Blanc/Héron
|-
|Aigrette sacrée
|[[wikt:fr:黒鷺|黒鷺]]
|くろさぎ
|kurosagi
|Noir/Héron
|-
|Alouette
|[[wikt:fr:雲雀|雲雀]]
|ひばり
|hibari
|Nuage/Moineau
|-
|Autruche
|[[wikt:fr:鴕鳥|鴕鳥]]
|だちょう
|dachō
|Autruche/Oiseau
|-
|Bécasse
|[[wikt:fr:山鴫|山鴫]]
|やましぎ
|yamashigi
|Montagne/Chevalier
|-
|Bihoreau goisagi
|[[wikt:fr:溝五位|溝五位]]
|みぞごい
|mizogoi
|Fossé/Cinq/Couronne
|-
|Bihoreau violacé
|[[wikt:fr:蓑五位|蓑五位]]
|みのごい
|minogoi
|Manteau de paille/Cinq/Couronne
|-
|Caille
|[[wikt:fr:鶉|鶉]]
|うずら
|uzura
|/Oiseau
|-
|Calao
|[[wikt:fr:犀鳥|犀鳥]]
|さいちょう
|saichō
|Rhinocéros/Oiseau
|-
|Calopsitte
|[[wikt:阿亀鸚哥|阿亀鸚哥]]
|おかめいんこ
|okame inko
|Femme inélégante/Perruche
|-
|Canard
|[[wikt:fr:鴨|鴨]] (sauvage)<br />[[wikt:fr:家鴨|家鴨]] (domestique)
|かも<br />あひる
|kamo<br />ahiru
|Enveloppe dure/Oiseau<br />Maison/Canard sauvage
|-
|Canard colvert
|[[wikt:fr:真鴨|真鴨]]
|まがも
|magamo
|Vrai/Canard sauvage
|-
|Canard mandarin
|[[wikt:fr:鴛鴦|鴛鴦]]
|おしどり
|oshidori
|/
|-
|Canaroie semipalmée
|[[wikt:fr:鵲雁|鵲雁]]
|かささぎがん
|kasasagigan
|Pie/Oie sauvage
|-
|Casoar
|[[wikt:fr:火食鳥|火食鳥]]
|ひくいどり
|hikuidori
|Feu/Mangeant/Oiseau
|-
|Cassican flûteur
|[[wikt:fr:鵲笛鴉|鵲笛鴉]]
|かささぎふえがらす
|kasasagi fuegarasu
|Pie/Flûte/Corbeau
|-
|Chouette
|[[wikt:fr:梟|梟]]
|ふくろう
|fukurō
|Oiseau/Arbre
|-
|Cigogne
|[[wikt:fr:鸛|鸛]]
|こうのとり
|kōnotori
|Enfant/Oiseau
|-
|Cigogne blanche
|[[wikt:fr:朱嘴鸛|朱嘴鸛]]
|しゅばしこう
|shubashikō
|Vermillon/Bec/Cigogne
|-
|Cigogne d'Abdim
|[[wikt:fr:青端鸛|青端鸛]]
|あおはしこう
|aohashikō
|Bleu/Extrémité/Cigogne
|-
|Cigogne épiscopale
|[[wikt:fr:白襟鸛|白襟鸛]]
|しろえりこう
|shiroerikō
|Blanc/Cou/Cigogne
|-
|Cigogne maguari
|[[wikt:fr:燕尾鸛|燕尾鸛]]
|えんびこう
|enbikō
|Hirondelle/Queue/Cigogne
|-
|Cigogne noire
|[[wikt:fr:鍋鸛|鍋鸛]]
|なべこう
|nabekō
|Chaudron/Cigogne
|-
|Colombe/Pigeon
|[[wikt:fr:鳩|鳩]]
|はと
|hato
|/Oiseau
|-
|Condor
|[[wikt:fr:公佗児|公佗児]]
|こうわじ
|kōwaji
|Public/Fier ; solitaire/ (On trouve également le terme 「コンドル」issu de l'anglais "condor".)
|-
|Coq de bruyère
|[[wikt:fr:雷鳥|雷鳥]]
|らいちょう
|raichō
|Tonnerre/Oiseau
|-
|Coq
|[[wikt:fr:雄鳥|雄鳥]]
|おんどり
|ondori
|Mâle/Oiseau
|-
|Corbeau
|[[wikt:fr:烏|烏]]
|からす
|karasu
|Terme général désignant les animaux du genre ''corvus''
|-
|Corneille mantelée
|[[wikt:fr:頭巾烏|頭巾烏]]
|ずきんがらす
|zukingarasu
|Capuche/Corbeau
|-
|Dinde
|[[wikt:fr:七面鳥|七面鳥]]
|しちめんちょう
|shichimenchō
|Sept visages/Oiseau
|-
|Faisan
|[[wikt:fr:雉|雉]]
|きじ
|kiji
|
|-
|Faucon
|[[wikt:fr:鷹|鷹]]
|たか
|taka
|
|-
|Faucon crécerelle
|[[wikt:fr:長元坊|長元坊]]
|ちょうげんぼう
|chōgenbō
|Chef/Fondation/Chambre
|-
|Faucon pèlerin
|[[wikt:fr:隼|隼]]
|はやぶさ
|hayabusa
|
|-
|Geai
|[[wikt:fr:懸巣|懸巣]]
|かけす
|kakesu
|/
|-
|Grand-duc de Blakiston
|[[wikt:fr:島梟|島梟]]
|しまふくろう
|shimafukurō
|Île/Chouette
|-
|Grèbe castagneux
|[[wikt:fr:鸊鷉|鸊鷉]]
|かいつぶり
|kaitsuburi
|/
|-
|Grive
|[[wikt:fr:鶇|鶇]]
|つぐみ
|tsugumi
|Tenir sa langue/Oiseau
|-
|Grue demoiselle
|[[wikt:fr:姉羽鶴|姉羽鶴]]
|あねはづる
|anehazuru
|Grande sœur/Aile/Grue
|-
|Grue
|[[wikt:fr:鶴|鶴]]
|つる
|tsuru
|S'élever/Oiseau
|-
|Grue royale
|[[wikt:fr:頬白冠鶴|頬白冠鶴]]
|ほおじろかんむりづる
|hōjiro kanmurizuru
|Bruant à longue queue/Grue couronnée
|-
|Harpie féroce
|[[wikt:fr:扇鷲|扇鷲]]
|おうぎわし
|ōgiwashi
|Éventail/Aigle
|-
|Héron cendré
|[[wikt:fr:蒼鷺|蒼鷺]]
|あおさぎ
|aosagi
|Bleu/Héron
|-
|Hibou
|[[wikt:fr:木菟|木菟]]
|みみずく
|mimizuku
|Arbre/Lapin
|-
|Hirondelle
|[[wikt:fr:燕|燕]]
|つばめ
|tsubame
|
|-
|Hirondelle bicolore
|[[wikt:fr:緑燕|緑燕]]
|みどりつばめ
|midori tsubame
|Vert/Hirondelle
|-
|Huppe fasciée
|[[wikt:fr:戴勝|戴勝]]
|やつがしら
|yatsugashira
|Serviteur/Tête
|-
|Lagopède alpin
|[[wikt:fr:雷鳥|雷鳥]]
|らいちょう
|raichō
|Foudre/Oiseau
|-
|Macareux moine
|[[wikt:fr:西角目鳥|西角目鳥]]
|にしつのめどり
|nishitsunomedori
|Ouest/Macareux cornu
|-
|Martinet
|[[wikt:fr:雨燕|雨燕]]
|あまつばめ
|amatsubame
|Pluie/Hirondelle
|-
|Moineau
|[[wikt:fr:雀|雀]]
|すずめ
|suzume
|
|-
|Mouette
|[[wikt:fr:鴎|鴎]]
|かもめ
|kamome
|Arrondissement citadin/Oiseau
|-
|Oie
|[[wikt:fr:鵞鳥|鵞鳥]]
|がちょう
|gachō
|Oie/Oiseau
|-
|Oiseau de proie
|[[wikt:fr:猛禽|猛禽]]
|もうきん
|mōkin
|Fureur/Oiseau
|-
|Oiseau
|[[wikt:fr:鳥|鳥]]
|とり
|tori
|
|-
|Palombe/Ramier
|[[wikt:fr:森鳩|森鳩]]
|もりばと
|moribato
|Forêt/Pigeon
|-
|Paon
|[[wikt:fr:孔雀|孔雀]]
|くじゃく
|kujaku
|Cavité/Moineau
|-
|Paradisier
|[[wikt:fr:風鳥|風鳥]]
|ふうちょう
|fūchō
|Vent/Oiseau
|-
|Perroquet
|[[wikt:fr:鸚鵡|鸚鵡]]
|おうむ
|ōmu
|
|-
|Perruche
|[[wikt:fr:鸚哥|鸚哥]]
|いんこ
|inko
|
|-
|Pie
|[[wikt:fr:鵲|鵲]]
|かささぎ
|kasasagi
|Autrefois/Oiseau
|-
|Plongeon imbrin
|[[wikt:fr:嘴黒阿比|嘴黒阿比]]
|はしぐろあび
|hashiguroabi
|Bec noir/Plongeon
|-
|Poule
|[[wikt:fr:雌鶏|雌鶏]]
|めんどり
|mendori
|Femelle/Poulet
|-
|Poulet
|[[wikt:fr:鶏|鶏]] (domestique)
|にわとり
|niwatori
|Jardin/Oiseau
|-
|Passerin indigo
|[[wikt:fr:瑠璃野路子|瑠璃野路子]]
|るりのじこ
|rurinojiko
|/Enfant
|-
|Roitelet
|[[wikt:fr:鷦鷯|鷦鷯]]
|みそさざい
|misosazai
|/
|-
|Roselin pourpré
|[[wikt:fr:紫猿子|紫猿子]]
|むらさきましこ
|murasaki mashiko
|Violet/Singe/Enfant
|-
|Rossignol
|[[wikt:fr:小夜啼鳥|小夜啼鳥]]
|さよなきどり
|sayonakidori
|Petite nuit/Oiseau chanteur
|-
|Rouge-gorge
|[[wikt:fr:駒鳥|駒鳥]]
|こまどり
|komadori
|Chevalet/Oiseau
|-
|Sarcelle
|[[wikt:fr:小鴨|小鴨]]
|こがも
|kogamo
|Petit/Canard sauvage
|-
|Sterne arctique
|[[wikt:fr:極鰺刺|極鰺刺]]
|きょくあじさし
|kyokuajisashi
|Extrême/Sterne
|-
|Sterne
|[[wikt:fr:鰺刺|鰺刺]]
|あじさし
|ajisashi
|Chinchard/
|-
|Toucan
|[[wikt:fr:大嘴|大嘴]]
|おおはし
|ōhashi
|Grand/Bec
|-
|Tourterelle
|[[wikt:fr:山鳩|山鳩]]
|やまばと
|yamabato
|Montagne/Colombe, pigeon
|-
|Tourterelle des bois
|[[wikt:fr:小雉鳩|小雉鳩]]
|こきじばと
|kokijibato
|Petit/Tourterelle occidentale
|-
|Vautour
|[[wikt:fr:禿鷹|禿鷹]]
|はげたか
|hagetaka
|Calvitie/Faucon
|}
== Mammifères ==
[[Image:Books-aj.svg aj ashton 01.svg|right|70px]]
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Agneau
|[[wikt:fr:子羊|子羊]]
|こひつじ
|kohitsuji
|Enfant/Mouton
|-
|Alpaga
|
|アルパカ
|arupaka
|
|-
|Âne
|[[wikt:fr:驢馬|驢馬]]
|ろば
|roba
|Ce terme s'écrit en katakana comme suit :「ロバ」
|-
|Babouin
|[[wikt:狒々|狒々]]
|ひひ
|hihi
|Issu de la créature légendaire du folklore japonais : ''[[w:Hi-hi|Hi-hi]]''
|-
|Baleine
|[[wikt:fr:鯨|鯨]]
|くじら
|kujira
|Ce terme s'écrit en katakana comme suit :「クジラ」
|-
|Bassaris rusé
|
|カコミスル
|kakomisuru
|Issu de l'anglais « cacomistle ».
|-
|Belette d’Europe
|[[wikt:fr:飯綱|飯綱]]
|いいづな
|īzuna
|Riz/corde. Désignait originellement une créature légendaire du nom de [[w:Kuda-kitsune|kuda-gitsune]].
|-
|Belette du Japon / Itatsi
|㹨
|いたち
|Itachi
|Désigne également les mustélidés en général. Ce terme s’écrit en katakana comme suit : 「イタチ」
|-
|Bélier
|[[wikt:雄羊|雄羊]]
|おひつじ
|ohitsuji
|Mâle/Mouton
|-
|Bison
|[[wikt:野牛|野牛]]
|やぎゅう
|yagyū
|Champ/Bœuf. Est surtout désigné sous le terme baizon 「バイゾン」
|-
|Blaireau
|[[wikt:fr:穴熊|穴熊]]
|あなぐま
|anaguma
|Trou (穴) Ours (熊). Ce terme s'écrit en katakana comme suit :「アナグマ」
|-
|Blaireau japonais
|[[wikt:fr:日本穴熊|日本穴熊]]
|にほんあなぐま
|Nihon anaguma
|Japon (日本) Blaireau (穴熊)
|-
|Bouc
|[[wikt:雄山羊|雄山羊]]
|おやぎ
|oyagi
|Mâle/Chèvre
|-
|Brebis
|[[wikt:雌羊|雌羊]]
|めひつじ
|mehitsuji
|Femelle/Mouton
|-
|Buffle
|水牛
|すいぎゅ
|suigyu
|Eau/Bœuf : Vient du buffle d’Eau d’Asie.
|-
|Castor
|
|ビーバー
|bībā
|Beaver (en)
|-
|Chameau
|[[wikt:fr:駱駝|駱駝]]
|らくだ
|rakuda
|
|-
|Chat
|[[wikt:fr:猫|猫]]
|ねこ
|neko
|
|-
|Chaton
|[[wikt:fr:子猫|子猫]]
|こねこ
|koneko
|Enfant/Chat
|-
|Chauve-souris
|[[wikt:fr:蝙蝠|蝙蝠]]
|こうもり
|kōmori
|
|-
|Chevreau
|[[wikt:子山羊|子山羊]]
|こやぎ
|koyagi
|Enfant/Chèvre
|-
|Chevreuil
|[[wikt:麕鹿|麕鹿]]
|のろじか
|norojika
|Chevreuil/Cerf
|-
|Cerf
|[[wikt:fr:鹿|鹿]]
|しか
|shika
|
|-
|Cerf du père David
|四不像
|ししふうぞう
|shifuzō
|Quatre/Négatif/Image (en tête). Du chinois « aucun des quatre »
|-
|Cerf sika
|日本鹿
|にほんじか
|Nihon jika
|Japon/Cerf
|-
|Chacal
|
|ジャッカル
|jakkaru
|jackal (en)
|-
|Cheval
|[[wikt:fr:馬|馬]]
|うま
|uma
|
|-
|Chèvre
|[[wikt:fr:山羊|山羊]]
|やぎ
|yagi
|Montagne (山) Mouton (羊)
|-
|Chien
|[[wikt:fr:犬|犬]]
|いぬ
|inu
|
|-
|Chien viverrin / Tanuki
|[[wikt:fr:狸|狸]]
|たぬき
|tanuki
|
|-
|Chinchilla
|
|チンチラ
|chinchira
|
|-
|Chiot
|[[wikt:fr:子犬|子犬]]
|こいぬ
|koinu
|Enfant (子) Chien (犬)
|-
|Civette
|[[wikt:fr:麝香猫|麝香猫]]
|じゃこうねこ
|jakōneko
|Musc (麝香) Chat (猫)
|-
|Coati
|鼻熊
|はなぐま
|hanaguma
|Nez (鼻) Ours (熊)
|-
|Cochon
|[[wikt:fr:豚|豚]]
|ぶた
|buta
|
|-
|Cochon d'Inde
|
|モルモット
|morumotto
|On trouve également le terme 「ギニアピッグ」
|-
|Coyote
|
|コヨーテ
|koyōte
|
|-
|Daim
|駝鹿
|ダマジカ
|damajika
|Daim/Cerf
|-
|Dasyure
|[[wikt:fr:袋猫|袋猫]]
|ふくろねこ
|fukuroneko
|Sac (袋) Chat (猫)
|-
|Dègue
|
|デグー
|degū
|
|-
|Dhole
|犲
|やまいぬ
|Yamainu
|Montagne/Chien. Confondu avec les chiens errants et les loups, est aujourd’hui plutôt désigné sous le nom de ''dōru'' 「ドール」
|-
|Dromadaire
|[[wikt:fr:一瘤駱駝|一瘤駱駝]]
|ひとこぶらくだ
|hitokobu rakuda
|Bosse unique/Chameau
|-
|Dugong / Vache marine
|儒艮
|じゅごん
|jugon
|
|-
|Échidné
|[[wikt:fr:針土竜|針土竜]]
|はりねずみ
|harimogura
|Aiguille/Taupe
|-
|Écureuil
|[[wikt:fr:栗鼠|栗鼠]]
|りす
|risu
|Noix (栗) Rat (鼠)
|-
|Écureuil volant
|[[wikt:fr:鼯鼠|鼯鼠]]
|ももんが
|momonga
|Écureuil volant (鼯) Rat (鼠)
|-
|Élan
|[[wikt:fr:箆鹿|箆鹿]]
|へらじか
|herajika
|Spatule (箆) Cerf (鹿)
|-
|Éléphant
|[[wikt:fr:象|象]]
|ぞう
|zō
|-
|Faon
|[[wikt:子鹿|子鹿]]
|こじか
|kojika
|Enfant/Cerf
|-
|Fouine
|[[wikt:fr:胸白貂|胸白貂]]
|むねしろてん
|muneshiroten
|Poitrail blanc/Martre
|-
|Furet
|[[wikt:fr:白鼬|白鼬]]
|しろいたち
|shiroitachi
|Blanc/Belette (On trouve également le terme「フェレット」issu de l'anglais "ferret".)
|-
|Gerbille de Mongolie
|[[wikt:fr:砂鼠|砂鼠]]
|すなねずみ
|sunanezumi
|Sable (砂) Souris (鼠)
|-
|Gibbon
|[[wikt:fr:手長猿|手長猿]]
|てながざる
|tenagazaru
|Main/Long/Singe
|-
|Gibbon à mains blanches
|[[wikt:fr:白手手長猿|白手手長猿]]
|しろててながざる
|shirote tenagazaru
|Blanc/Main/Long/Singe
|-
|Girafe
|[[wikt:fr:麒麟|麒麟]]
|きりん
|kirin
|Issue de la créature légendaire du folklore assiatique : [[w:Qilin|Qilin]]
|-
|Glouton
|[[wikt:fr:屈狸|屈狸]]
|くずり
|kuzuri
|Ce terme s'écrit en katakana comme suit:「クズリ」
|-
|Gorille
|
|ゴリラ
|gorira
|Gorilla (en)
|-
|Grizzly
|[[wikt:fr:灰色熊|灰色熊]]
|はいいろぐま
|haīroguma
|Gris/Ours
|-
|Guépard
|[[wikt:fr:狩猟豹|狩猟豹]]
|しゅりょうひょう
|shuryōhyō
|Chasse/Panthère (On trouve également le terme 「チーター」issu de l'anglais "cheetah".)
|-
|Hamster
|
|ハムスター
|hamusutā
|Hamster (fr)
|-
|Hermine
|[[wikt:fr:白鼬|白鼬]]
|おこじょ
|okojo
|Blanc/Belette
|-
|Hérisson
|[[wikt:fr:針鼠|針鼠]]
|はりねずみ
|harinezumi
|Aiguille (針) Rat (鼠)
|-
|Hippopotame
|[[wikt:fr:河馬|河馬]]
|かば
|kaba
|Fleuve (河) Cheval (馬)
|-
|Kinkajou
|
|キンカジュー
|kinkajū
|Variante de quincajou.
|-
|Lama
|
|ラマ
|rama
|Lama (fr)
|-
|Lamantin
|
|マナティー
|manatī
|Issu de l'anglais "manatee".
|-
|Lapin
|[[wikt:fr:兎|兎]]
|うさぎ
|usagi
|Terme général désignant les lagomorphes
|-
|Léopard/Panthère
|[[wikt:豹#ja|豹]]
|ひょう
|hyō
|
|-
|Lièvre
|[[wikt:fr:野兎|野兎]]
|のうさぎ
|nousagi
|Champ (野) Lapin (兎)
|-
|Lion
|[[wikt:fr:獅子|獅子]]
|しし
|shishi
|Le terme 「ライオン」 issu de l'anglais "lion" est aujourd'hui plus largement utilisé.
|-
|Loup
|[[wikt:fr:狼|狼]]
|おおかみ
|ōkami
|
|-
|Loutre
|[[wikt:fr:川獺|川獺]]
|かわうそ
|kawauso
|Rivère (川) Loutre (獺)
|-
|Loutre de mer
|海獺
|らっこ
|rakko
|Mer/Loutre
|-
|Loutre japonaise
|[[wikt:fr:日本川獺|日本川獺]]
|にほんあなぐま
|Nihon kawauso
|Japon (日本) Loutre (獺) (Cette espèce est aujourd’hui éteinte.)
|-
|Lycaon
|
|リカオン
|rikaon
|
|-
|Lynx
|[[wikt:fr:大山猫|大山猫]]
|おおやまねこ
|ōyamaneko
|Grand (大) Montagne (山) Chat (猫)
|-
|Marmotte
|
|ウッドチャック
|uddochakku
|Issu de l'anglais "woodchuck".
|-
|Martre
|貂
|てん
|ten
|
|-
|Mouffette
|
|スカンク
|sukanku
|Skunk (en)
|-
|Mouton
|[[wikt:fr:羊|羊]]
|ひつじ
|hitsuji
|
|-
|Mule
|[[wikt:fr:騾馬|騾馬]]
|らば
|raba
|Cheval (馬)
|-
|Mulet
|[[wikt:fr:駃騠|駃騠]]
|けってい
|kettei
|
|-
|Narval
|[[wikt:一角|一角]]
|いっかく
|ikkaku
|Une (一), Corne (角)
|-
|Numbat
|[[wikt:袋蟻食|袋蟻食]]
|ふくろありくい
|fukuroarikui
|Poche/Fourmilier
|-
|Opossum
|
|[[wikt:オポッサム|オポッサム]]
|opossamu
|Opossum (en)
|-
|Ornithorynque
|[[wikt:fr:鴨嘴|鴨嘴]]
|かものはし
|kamonohashi
|Canard/Bec
|-
|Ours
|[[wikt:fr:熊|熊]]
|くま
|kuma
|
|-
|Ours brun
|羆
|ひぐま
|higuma
|
|-
|Ours noir d’Asie
|月輪熊
|ツキノワグマ
|tsukinowaguma
|Lune/Anneau/Ours. Est parfois raccourci par ''tsukiguma'' (月熊)
|-
|Ours polaire
|[[wikt:fr:白熊|白熊]]
|しろくま
|shirokuma
|Blanc (白) Ours (熊)
|-
|Panda
|
|パンダ
|panda
|Panda (fr)
|-
|Paresseux
|[[wikt:樹懶|樹懶]]
|なまけもの
|namakemono
|
|-
|Petit panda
|
|レッサーパンダ
|ressā panda
|Issu de l'anglais "lesser panda".
|-
|Porc-épic
|[[wikt:fr:山荒|山荒]]
|やまあらし
|yamaarashi
|Montagne (山) Stérile (荒)
|-
|Protèle
|[[wikt:fr:土狼|土狼]]
|つちおおかみ
|tsuchiōkami
|Terre/Loup (On trouve également le terme 「アードウルフ」issu de l'anglais "aardwolf".)
|-
|Puma
|
|ピューマ
|pyūma
|Puma (en)
|-
|Putois
|[[wikt:fr:毛長鼬|毛長鼬]]
|けながいたち
|kenagaitachi
|Poil long/Belette
|-
|Rat
|[[wikt:fr:熊鼠|熊鼠]]
|くまねずみ
|kumanezumi
|Ours/Souris
|-
|Raton laveur
|[[wikt:fr:洗熊|洗熊]]
|あらいぐま
|araiguma
|Laver/Ours
|-
|Renard
|[[wikt:fr:狐|狐]]
|きつね
|kitsune
|
|-
|Renard argenté
|[[wikt:fr:銀狐|銀狐]]
|ぎんぎつね
|gingitsune
|Argent (銀) Renard (狐)
|-
|Renard gris
|[[wikt:fr:灰色狐|灰色狐]]
|はいいろぎんぎつね
|haīro gitsune
|Cendre (灰) Couleur (色) Renard (狐)
|-
|Rhinoceros
|[[wikt:fr:犀|犀]]
|さい
|sai
|
|-
|Sanglier
|[[wikt:fr:猪|猪]]
|いのしし
|inoshishi
|
|-
|Saro
|鴨鹿
|カモシカ
|kamoshika
|Canard sauvage/Cerf. Les kanjis ne sont jamais utilisés, le nom est écrit exclusivement en katakana
|-
|Siamang
|[[wikt:fr:袋手長猿|袋手長猿]]
|ふくろてながざる
|fukurotenagazaru
|Sac/Main/Long/Singe
|-
|Singe
|[[wikt:fr:猿|猿]]
|さる
|saru
|
|-
|Souris
|[[wikt:fr:鼠|鼠]]
|ねずみ
|nezumi
|
|-
|Suricate
|
|ミーアキャット
|mīakyatto
|Meerkat (en)
|-
|Tamia
|[[wikt:fr:縞栗鼠|縞栗鼠]]
|しまりす
|shimarisu
|Rayure/Écureuil
|-
|Tapir
|[[wikt:fr:獏|獏]]
|ばく
|baku
|Issu de la créature légendaire : [[w:Baku|baku]], elle même issue des textes chinois sur le tapir.
|-
|Taupe
|[[wikt:fr:土竜|土竜]]
|もぐら
|mogura
|Terre/Dragon
|-
|Taureau
|[[wikt:fr:雄牛|雄牛]]
|おうし
|oushi
|Mâle (雄) Vache (牛)
|-
|Tigre
|[[wikt:fr:虎|虎]]
|とら
|tora
|
|-
|Vache
|[[wikt:fr:牛|牛]]
|うし
|ushi
|
|-
|Veau
|[[wikt:fr:子牛|子牛]]
|こうし
|koushi
|Enfant (子) Vache (牛)
|-
|Vison
|
|ミンク
|minku
|mink (en)
|-
|Zèbre
|[[wikt:fr:縞馬|縞馬]]
|しまうま
|shimauma
|Rayure (縞) Cheval (馬)
|-
|Zèbre de montagne
|[[wikt:fr:山縞馬|山縞馬]]
|やましまうま
|yamashimauma
|Montagne (山) Zèbre (縞馬)
|-
|Zibeline
|[[wikt:fr:黒貂|黒貂]]
|くろてん
|kuroten
|Noir (黒) Martre (貂)
|}
== Reptiles, poissons et autres vies marines ==
{| {{tableau_japonais}}
! Français !! [[Japonais/Kanji|Kanji]] !! [[Japonais/Kana|Kana]] !! [[Japonais/Romaji|Rōmaji]] !! Sens littéral
|-
|Anguille
|[[wikt:fr:鰻|鰻]]
|うなぎ
|unagi
|
|-
|Barracuda
|[[wikt:fr:梭子魚|梭子魚]]
|かます
|kamasu
|Navette/Enfant/Poisson
|-
|Calamar
|[[wikt:fr:墨魚|墨魚]]
|いか
|ika
|Encre/Poisson
|-
|Carpe
|[[wikt:fr:鯉|鯉]]
|こい
|koi
|
|-
|Concombre de mer
|[[wikt:海鼠|海鼠]]
|なまこ
|namako
|Mer/Rat
|-
|Congre
|[[wikt:fr:穴子|穴子]]
|あなご
|anago
|Trou/Enfant
|-
|Crabe
|[[wikt:fr:蟹|蟹]]
|かに
|kani
|
|-
|Crabe de cocotier
|[[wikt:fr:椰子蟹|椰子蟹]]
|やしがに
|yashigani
|Palmier/Crabe
|-
|Crapaud
|[[wikt:蟇蛙|蟇蛙]]
|ひきがえる
|hikigaeru
|Crapaud/Grenouille
|-
|Crevette
|[[wikt:fr:蝦|蝦]]
|えび
|ebi
|
|-
|Crocodile
|[[wikt:fr:鰐|鰐]]
|わに
|wani
|
|-
|Dauphin
|[[wikt:fr:海豚|海豚]]
|いるか
|iruka
|Mer/Cochon
|-
|Dragon
|[[wikt:fr:竜|竜]]
|りゅう
|ryū
|Pour parler d'un dragon occidental, on emploiera「ドラゴン」.
|-
|Dragon de Komodo
|
|コモドドラゴン
|Komodo-doragon
|Komodo Dragon (en)
|-
|Escargot
|[[wikt:fr:蝸牛|蝸牛]]
|かたつむり
|katatsumuri
|Escargot/Vache
|-
|Gecko
|[[wikt:守宮|守宮]]
|やもり
|yamori
|Protection/Palais
|-
|Gecko léopard
|[[wikt:豹紋蜥蜴擬き|豹紋蜥蜴擬き]]
|ひょうもんとかげもどき
|hyōmon'tokagemodoki
|/Lézard/
|-
|Grenouille
|[[wikt:fr:蛙|蛙]]
|かえる
|kaeru
|
|-
|Iguane
|
|イグアナ
|iguana
|Iguana(en)
|-
|Léopard de mer
|[[wikt:fr:豹海豹|豹海豹]]
|ひょうあざらし
|hyōazarashi
|Panthère/Phoque
|-
|Marsouin
|[[wikt:fr:鼠海豚|鼠海豚]]
|ねずみいるか
|nezumīruka
|Souris/Dauphin
|-
|Méduse
|[[wikt:fr:水母|水母]]/[[wikt:fr:海月|海月]]
|くらげ
|kurage
|Eau/Mère ; Mer/Lune
|-
|Morse
|[[wikt:fr:海象|海象]]
|せいうち
|seiuchi
|Mer/Éléphant
|-
|Murène
|[[wikt:鱓|鱓]]
|うつぼ
|utsubo
|/
|-
|Orque
|[[wikt:fr:鯱|鯱]]
|しゃち
|shachi
|/
|-
|Otarie
|[[wikt:fr:海驢|海驢]]
|あしか
|ashika
|Mer/Âne
|-
|Phoque
|[[wikt:fr:海豹|海豹]]
|あざらし
|azarashi
|Mer/Panthère
|-
|Pieuvre
|[[wikt:fr:蛸|蛸]]
|たこ
|tako
|
|-
|Poisson
|[[wikt:fr:魚|魚]]
|さかな
|sakana
|
|-
|Poisson rouge
|[[wikt:fr:金魚|金魚]]
|きんぎょ
|kingyo
|Or/Poisson
|-
|Raie
|[[wikt:fr:鱝|鱝]]
|えい
|ei
|Poisson/Orné
|-
|Requin
|[[wikt:fr:鮫|鮫]]
|さめ
|same
|Poisson/Croisement
|-
|Requin-baleine
|甚兵衛鮫
|じんべいざめ
|jinbeizame
|Jinbei/Requin
|-
|Requin-renard
|[[wikt:fr:尾長鮫|尾長鮫]]
|おながざめ
|onagazame
|Longue queue/Requin
|-
|Requin-tigre
|[[wikt:fr:鼬鮫|鼬鮫]]
|いたちざめ
|itachizame
|Belette/Requin
|-
|Salamandre
|[[wikt:fr:蠑螈|蠑螈]]
|いもり
|imori
|/
|-
|Sardine
|[[wikt:鰯|鰯]]
|いわし
|iwashi
|Poisson/Faible
|-
|Serpent
|[[wikt:fr:蛇|蛇]]
|へび
|hebi
|
|-
|Thon
|[[wikt:fr:鮪|鮪]]
|まぐろ
|maguro
|Poisson/Bleu
|-
|Tortue
|[[wikt:fr:亀|亀]]
|かめ
|kame
|
|-
|Tortue de mer
|[[wikt:fr:海亀|海亀]]
|うみがめ
|umigame
|Mer/Tortue
|}
== Voir aussi ==
*Une série d'[[Japonais/Exercices de vocabulaire#Animaux|exercices]] sur les animaux est également disponible.
*[[wikt:fr:Catégorie:Noms_communs_japonais|Les noms dans le wiktionnaire]]
{{Glossaires_de_Japonais}}
[[Catégorie:Glossaires de Japonais]]
[[en:Japanese/Vocabulary/Animals]]
[[es:Japonés/Vocabulario/Animales]]
5pii42gqeor8mwz8b4i86076plhw5kt
Japonais/Vocabulaire/Nourriture
0
5162
772016
757578
2026-09-11T19:58:38Z
~2026-49266-35
124543
/* Nourriture */
772016
wikitext
text/x-wiki
[[Image:Books-aj.svg aj ashton 01.svg|right|70px]]
==Nourriture==
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Avoine
|[[wikt:ja:烏麦|烏麦]]
|からすむぎ
|karasumugi
|
|-
|Baguette de pain
|
|バゲット
|bagetto
|
|-
|Blé
|[[wikt:ja:小麦|小麦]]
|こむぎ
|komugi
|-
|Bœuf
|[[w:ja:牛肉|牛肉]]
|ぎゅうにく
|gyūniku
|Vache/Viande
|-
|Carotte
|[[w:ja:人参|人参]]
|にんじん
|ninjin
|-
|Chou
|[[wikt:甘藍|甘藍]]<br>[[wikt:玉菜|玉菜]]
|かんらん<br>たまな<br>キャベツ
|kanran<br>tamana<br>kyabetsu
|-
|Courgette
|
|[[wikt:ズッキーニ|ズッキーニ]]
|zukkīni
|-
|Fruit
|[[w:ja:果物|果物]]
|くだもの
|kudamono
|
|-
|Légume
|[[w:ja:野菜|野菜]]
|やさい
|yasai
|
|-
|Navet
|[[w:ja:蕪|蕪]]
|かぶ
|kabu
|-
|Nourriture
|[[w:ja:食べ物|食べ物]]
|たべもの
|tabemono
|Manger/Chose
|-
|Oignon
|[[w:ja:玉葱|玉葱]]
|たまねぎ
|tamanegi
|-
|Œuf
|[[w:ja:卵|卵]]
|たまご
|tamago
|
|-
|Orge
|[[wikt:ja:大麦|大麦]]
|おおむぎ
|ōmugi
|-
|Pain
|[[w:ja:麺麭|麺麭]]
|パン
|pan
|
|-
|Patate douce
|[[wikt:ja:薩摩芋|薩摩芋]]
|さつまいも
|Satsuma imo
|Satsuma/pomme de terre (Satsuma était une ancienne province japonaise située sur l'île de Kyūshū, dans l'actuelle préfecture de Kagoshima.)
|-
|Poireau
|[[wikt:fr:葱|葱]]
|ねぎ
|negi
|
|-
|Poisson
|[[w:ja:魚|魚]]
|さかな
|sakana
|
|-
|Poivre
|[[w:ja:胡椒|胡椒]]
|こしょう
|koshō
|
|-
|Pois
|[[wikt:fr:豌豆|豌豆]]
|えんどう
|endō
|
|-
|Pomme d’amour
|[[wikt:fr:林檎飴|林檎飴]]
|りんごあめ
|ringoame
|Pomme/Bonbon
|-
|Pomme de terre
|[[wikt:fr:馬鈴薯|馬鈴薯]]
|じゃがいも
|jagaimo
|
|-
|Porc
|[[w:ja:豚肉|豚肉]]
|ぶたにく
|butaniku
|Porc/Viande
|-
|Poulet
|[[w:ja:鳥肉|鳥肉]]
|とりにく
|toriniku
|Oiseau/Viande
|-
|Radis blanc
|[[wikt:fr:大根|大根]]
|だいこん
|daikon
|Grand/Racine
|-
|Riz (non-cuisiné)
|[[w:ja:米|米]]
|こめ
|kome
|
|-
|Riz (cuisiné)
|[[w:ja:御飯|御飯]]
|ごはん
|gohan
|
|-
|Sarrasin
|[[wikt:ja:蕎麦|蕎麦]]
|そば
|soba
|-
|Sel
|[[w:ja:塩|塩]]
|しお
|shio
|
|-
|Soupe
|[[w:ja:汁物|汁物]]
|しるもの
|shirumono
|
|-
|Sucre
|[[w:ja:砂糖|砂糖]]
|さとう
|satō
|
|-
|Viande
|[[w:ja:肉|肉]]
|にく
|niku
|
|}
* Note : en japonais autochtone, il n'y a pas de distinction entre la soupe et le jus. Cependant, les anglicismes ジュース ("juice", pour désigner le jus) et スープ ("soup", pour désigner la soupe) sont largement employés aujourd'hui.
=== Fruits ===
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Abricot
|[[wikt:fr:杏子|杏子]]
|あんず
|anzu
|/Enfant
|-
|Ananas
|[[wikt:fr:鳳梨|鳳梨]]
|ほうり<br>パイナップル
|hōri<br>painappuru
|
|-
|Aubergine
|[[wikt:fr:茄子|茄子]]
|なす
|nasu
|
|-
|Banane
|[[wikt:fr:甘蕉|甘蕉]]
|かんしょう
|kanshō
|/ (On trouve également le terme 「バナナ」issu de l'anglais "banana".)
|-
|Cerise
|[[w:ja:桜ん坊|桜ん坊]]
|さくらんぼう
|sakuranbō
|/
|-
|Citron
|[[w:ja:檸檬|檸檬]]
|レモン
|remon
|
|-
|Citrouille
|[[w:ja:南瓜|南瓜]]
|かぼちゃ
|kabocha
|
|-
|Figue
|[[w:ja:無花果|無花果]]
|いちじく
|ichijiku
|Sans/Fleur/Fruit
|-
|Fraise
|[[w:ja:苺|苺]]
|いちご
|ichigo
|
|-
|Framboise
|
|[[w:ja:ラズベリー|ラズベリー]]
|razuberī
|Raspberry (en)
|-
|Fruit
|[[wikt:fr:果物|果物]]
|くだもの
|kudamono
|Fruit/Chose
|-
|Jamalac
|[[wikt:fr:蓮霧|蓮霧]]
|レンブ
|renbu
|/
|-
|Mandarine
|[[w:ja:蜜柑|蜜柑]]
|みかん
|mikan
|
|-
|Mûre
|
|[[wikt:ブラックベリー|ブラックベリー]]
|burakkuberī
|Blackberry (en)
|-
|Myrtille
|
|[[w:ja:ブルーベリー|ブルーベリー]]
|burūberī
|Blueberry (en)
|-
|Noix de coco
|
|ココナッツ
|kokonattsu
|-
|Orange
|[[w:ja:橙|橙]]
|だいだい
|daidai
|オレンジ est beaucoup plus employé.
|-
|Pamplemousse
|
|グレープフルーツ
|gurēpufurūtsu
|Grapefruit (en)
|-
|Pastèque
|[[wikt:fr:西瓜|西瓜]]
|すいか
|suika
|
|-
|Pêche
|[[wikt:fr:桃|桃]]
|もも
|momo
|
|-
|Poire
|[[wikt:fr:梨|梨]]
|なし
|nashi
|
|-
|Pomelo
|[[wikt:fr:文旦|文旦]]
|ぶんたん
|buntan
|/
|-
|Pomme
|[[wikt:fr:林檎|林檎]]
|りんご
|ringo
|
|-
|Prune japonaise
|[[wikt:fr:梅|梅]]
|うめ
|ume
|
|-
|Raisin
|[[wikt:fr:葡萄|葡萄]]
|ぶどう
|budō
|-
|Tomate
|[[wikt:fr:蕃茄|蕃茄]]
|ばんか
|banka
|/ (On trouve également le terme 「トマト」issu de l'anglais "tomato".)
|}
== Boissons ==
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Boisson
|[[w:ja:飲み物|飲み物]]
|のみもの
|''nomimono''
|Boire/Chose
|-
|Café
|珈琲
|コーヒー
|''kōhī''
|coffee (en)
|-
|Eau
|水
|みず
|''mizu''
|eau
|-
|Jus
|汁
|しる / ジュース
|''jūsu''
|Juice (en)
|-
|Jus d'orange
|橙汁
|だいだいしる /
オレンジジュース
|''daidaishiru /''
''orenjijūsu''
|Orange juice (en)
|-
|Lait
|[[w:ja:牛乳|牛乳]]
|ぎゅうにゅう
|''gyūnyū''
|Vache/lait
|-
|Thé noir
|[[w:ja:紅茶|紅茶]]
|こうちゃ
|''kōcha''
|rouge/thé
|-
|Thé vert
|[[w:ja:御茶|御茶]]
|おちゃ
|''(o)cha''
|???/thé
|}
<nowiki>*</nowiki>''daidaishiru est aujourd'hui désuet et est remplacé par l'anglicisme'' ''orenjijūsu''
=== Alcool ===
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Alcool
|[[w:ja:酒|酒]]
|さけ<br>アルコール
|''sake''<br>''arukōru'' (en)
|
|-
|Bière
|[[wikt:麦酒|麦酒]]
|むぎさけ<br>ビール
|''mugisake''<br>''bīru''
|beer (en)
|-
|Bière pression
|[[wikt:生麦酒|生麦酒]]<br>[[w:ja:生ビール|生ビール]]
|なまむぎさけ<br>なまビール
|''namamugisake''<br>''namabīru''
|
|-
|Champagne
|
|シャンパン
|''shanpan''
|
|-
|Eau de vie
|
|ブランデー
|''burandē''
|
|-
|Gin
|
|ジン
|''jin''
|
|-
|Liqueur,alcool distillé
|[[w:ja:焼酎|焼酎]]
|しょうちゅう
|''shōchū''
|
|-
|Rhum
|
|ラム
|''ramu''
|
|-
|Saké
|[[w:ja:日本酒|日本酒]]
|にほんしゅ
|''nihonshu''
|Japon/Alcool
|-
|Scotch
|
|スコッチ
|''sukotchi''
|
|-
|Tequila
|
|テキーラ
|''tekīra''
|
|-
|Vin
|
|ワイン
|''wain''
|Wine (en)
|-
|Vin blanc
|[[w:ja:白ワイン|白ワイン]]
|しろワイン
|''shirowain''
|Blanc/Wine (en)
|-
|Vin rouge
|[[w:ja:赤ワイン|赤ワイン]]
|あかワイン
|''akawain''
|Rouge/Wine (en)
|-
|Vin de prune
|[[w:ja:梅酒|梅酒]]
|うめしゅ
|''umeshu''
|
|-
|Vodka
|
|ヴォッカ
|''vokka''
|
|-
|Whiskey
|
|ウィスキー
|''wisukī''
|
|}
==Plats typiques japonais==
*鹿尾菜の煮物 (hijiki no nimono) : algues noires bouillies.
*蒲焼き (kabayaki) : anguille grillée.
*肉じゃが ([[Livre de cuisine/Nikujaga|nikujaga]]) : bœuf ou porc, pommes de terre et oignons.
*串揚げ (kushiage) : brochettes panées de viande, de poisson ou de légumes.
*焼き鳥 (yakitori) : brochettes de poulet grillé.
*金平 (kin'pira) : carottes et bardanes râpées dans de la sauce soja.
*豚カツ (ton'katsu) : côtelettes de porc panées.
*コロッケ (korokke) : croquettes de pomme de terre.
*懐石料理 (kaisekiryōri) : dîner accompagnant la cérémonie du thé.
*鉄板焼き (teppan'yaki) : grillades de viande, légumes et poisson.
*野菜炒め (yasaītame) : légumes sautés.
*鋤焼き (sukiyaki) : marmite de viande et de légumes bouillis.
*饂飩 (udon) : nouilles à la farine de blé.
*蕎麦 (soba) : nouilles à la farine de sarrasin.
*天麩羅 (tenpura) : poisson et légumes frits.
*寿司 (sushi) : poisson cru sur du riz vinaigré.
*刺し身 (sashimi) : poisson cru en lamelles.
*御田 (oden) : pot-au-feu japonais.
*鳥の唐揚げ (tori no karāge) : poulet frit.
*茶碗蒸し (chawanmushi) : soupe crémeuse à base d'œuf.
*カレー (karē) : riz et [[Livre de cuisine/Curry japonais|curry]].
*豚汁 (ton'jiru) : soupe de porc au miso.
*お握り (onigiri) : sushi moulé à la main.
*拉麺 (rāmen) : nouilles accompagnées d'un bouillon à base de poisson ou de viande.
*付け麵 (tsukemen) : nouilles froides
*素麺 (sōmen) : nouilles japonaises très fines et blanches
*焼き蕎麦 (yakisoba) : nouilles sautées.
===Pâtisseries===
*霰 (arare) : biscuit de riz.
*花林糖 (karin'tō) : biscuit sucré à la farine de blé.
*御萩 (ohagi) : boulettes de riz et de pâte de haricots rouges sucrés.
*団子 (dan'go) : boulette de farine de riz.
*煎餅 (sen'bei) : galette de riz.
*海老煎餅 (ebisen'bei) : galette de riz aux crevettes.
*饅頭 (man'jū) : gâteaux fourrés à la pâte de haricot rouge.
*カステラ (kasutera) : gâteau mousseline.
*最中 (monaka) : gaufrettes fourrées à la pâte de haricot rouge.
*葛餅 (kuzumochi) : gelée de marante.
*大判焼き (ōban'yaki) : pancake chaud fourré à la pâte de haricot rouge.
*銅鑼焼き (dorayaki) : pancake froid fourré à la pâte de haricot rouge.
*羊羹 (yōkan) : pâte de haricot rouge gélifiée.
*餡パン (an'pan) : pain fourré à la pâte de haricot rouge.
*餅 (mochi) : gâteau de riz gluant.
*柏餅 (kashiwa-mochi) : gâteau de riz gluant fourré à la pâte de haricot rouge.
*メロンパン (meron'pan) : brioche fourrée.
==La cuisine chinoise et coréenne==
*焼き肉 (yakiniku) : barbecue à la coréenne
*キムチ (kimchi) : chou chinois pimenté à la coréenne
*餃子 (gyōza) : raviolis au porc et aux légumes
*焼売 (shūmai) : raviolis de porc à la vapeur
*ビビンバ (bibin'ba) : riz, œuf et légumes dans un bol
*炒飯 (chāhan) : riz sauté
*叉焼 (chāshū) : char siu
*春巻き (harumaki) : rouleau à l’œuf
*油条(yujō) : you tiao
==Plats occidentaux==
*グラタン (guratan) : gratin
*ハンバーガー (han'bāgā) : hamburger
*バナナスプリット (banana supuritto) : banana split
*ピザ (piza) : pizza
*フレンチトースト (Furenchi tōsuto) : pain perdu
*ポタージュ (potāju) : potage
*シチュー (shichū) : ragout
*チキンご飯 (chikin'gohan) : riz sauté avec du poulet
*チーズバーガー (chīzubāgā) : cheeseburger
*ステーキ (sutēki) : rumsteck
*サラダ (sarada) : salade
*サンドイッチ (san'doitchi (anciennement サンドヰッチ)) : sandwich
*スパゲティー (supagetī) : spaghettis
==Repas==
*朝食 (chōshoku) : petit déjeuner
*昼食 (chūshoku) : déjeuner
*軽食 (keishoku) : en-cas
*夕食 (yūshoku) : dîner
== Voir aussi ==
*[[wikt:Catégorie:Noms communs japonais|Les noms dans le wiktionnaire]]
{{Glossaires de Japonais}}
[[Catégorie:Glossaires de Japonais]]
[[en:Japanese/Vocabulary/Food and Drink]]
j6qmyxrfacg1kqnkka68ut4xqh845hx
772017
772016
2026-09-11T20:07:58Z
~2026-49266-35
124543
/* Fruits */
772017
wikitext
text/x-wiki
[[Image:Books-aj.svg aj ashton 01.svg|right|70px]]
==Nourriture==
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Avoine
|[[wikt:ja:烏麦|烏麦]]
|からすむぎ
|karasumugi
|
|-
|Baguette de pain
|
|バゲット
|bagetto
|
|-
|Blé
|[[wikt:ja:小麦|小麦]]
|こむぎ
|komugi
|-
|Bœuf
|[[w:ja:牛肉|牛肉]]
|ぎゅうにく
|gyūniku
|Vache/Viande
|-
|Carotte
|[[w:ja:人参|人参]]
|にんじん
|ninjin
|-
|Chou
|[[wikt:甘藍|甘藍]]<br>[[wikt:玉菜|玉菜]]
|かんらん<br>たまな<br>キャベツ
|kanran<br>tamana<br>kyabetsu
|-
|Courgette
|
|[[wikt:ズッキーニ|ズッキーニ]]
|zukkīni
|-
|Fruit
|[[w:ja:果物|果物]]
|くだもの
|kudamono
|
|-
|Légume
|[[w:ja:野菜|野菜]]
|やさい
|yasai
|
|-
|Navet
|[[w:ja:蕪|蕪]]
|かぶ
|kabu
|-
|Nourriture
|[[w:ja:食べ物|食べ物]]
|たべもの
|tabemono
|Manger/Chose
|-
|Oignon
|[[w:ja:玉葱|玉葱]]
|たまねぎ
|tamanegi
|-
|Œuf
|[[w:ja:卵|卵]]
|たまご
|tamago
|
|-
|Orge
|[[wikt:ja:大麦|大麦]]
|おおむぎ
|ōmugi
|-
|Pain
|[[w:ja:麺麭|麺麭]]
|パン
|pan
|
|-
|Patate douce
|[[wikt:ja:薩摩芋|薩摩芋]]
|さつまいも
|Satsuma imo
|Satsuma/pomme de terre (Satsuma était une ancienne province japonaise située sur l'île de Kyūshū, dans l'actuelle préfecture de Kagoshima.)
|-
|Poireau
|[[wikt:fr:葱|葱]]
|ねぎ
|negi
|
|-
|Poisson
|[[w:ja:魚|魚]]
|さかな
|sakana
|
|-
|Poivre
|[[w:ja:胡椒|胡椒]]
|こしょう
|koshō
|
|-
|Pois
|[[wikt:fr:豌豆|豌豆]]
|えんどう
|endō
|
|-
|Pomme d’amour
|[[wikt:fr:林檎飴|林檎飴]]
|りんごあめ
|ringoame
|Pomme/Bonbon
|-
|Pomme de terre
|[[wikt:fr:馬鈴薯|馬鈴薯]]
|じゃがいも
|jagaimo
|
|-
|Porc
|[[w:ja:豚肉|豚肉]]
|ぶたにく
|butaniku
|Porc/Viande
|-
|Poulet
|[[w:ja:鳥肉|鳥肉]]
|とりにく
|toriniku
|Oiseau/Viande
|-
|Radis blanc
|[[wikt:fr:大根|大根]]
|だいこん
|daikon
|Grand/Racine
|-
|Riz (non-cuisiné)
|[[w:ja:米|米]]
|こめ
|kome
|
|-
|Riz (cuisiné)
|[[w:ja:御飯|御飯]]
|ごはん
|gohan
|
|-
|Sarrasin
|[[wikt:ja:蕎麦|蕎麦]]
|そば
|soba
|-
|Sel
|[[w:ja:塩|塩]]
|しお
|shio
|
|-
|Soupe
|[[w:ja:汁物|汁物]]
|しるもの
|shirumono
|
|-
|Sucre
|[[w:ja:砂糖|砂糖]]
|さとう
|satō
|
|-
|Viande
|[[w:ja:肉|肉]]
|にく
|niku
|
|}
* Note : en japonais autochtone, il n'y a pas de distinction entre la soupe et le jus. Cependant, les anglicismes ジュース ("juice", pour désigner le jus) et スープ ("soup", pour désigner la soupe) sont largement employés aujourd'hui.
=== Fruits ===
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Abricot
|[[wikt:fr:杏子|杏子]]
|あんず
|anzu
|Abricot/Enfant
|-
|Ananas
|[[wikt:fr:鳳梨|鳳梨]]
|ほうり
|hōri
|/ (On trouve également le terme「パイナップル」issu de l'anglais "pineapple".)
|-
|Aubergine
|[[wikt:fr:茄子|茄子]]
|なす
|nasu
|
|-
|Banane
|[[wikt:fr:甘蕉|甘蕉]]
|かんしょう
|kanshō
|/ (On trouve également le terme 「バナナ」issu de l'anglais "banana".)
|-
|Cerise
|[[w:ja:桜ん坊|桜ん坊]]
|さくらんぼう
|sakuranbō
|/
|-
|Citron
|[[w:ja:檸檬|檸檬]]
|ねいもう
|neimō
|/ (On trouve également le terme「レモン」issu de l'anglais "lemon".)
|-
|Citrouille
|[[w:ja:南瓜|南瓜]]
|かぼちゃ
|kabocha
|
|-
|Figue
|[[w:ja:無花果|無花果]]
|いちじく
|ichijiku
|Sans/Fleur/Fruit
|-
|Fraise
|[[w:ja:苺|苺]]
|いちご
|ichigo
|
|-
|Framboise
|
|[[w:ja:ラズベリー|ラズベリー]]
|razuberī
|Raspberry (en)
|-
|Fruit
|[[wikt:fr:果物|果物]]
|くだもの
|kudamono
|Fruit/Chose
|-
|Jamalac
|[[wikt:fr:蓮霧|蓮霧]]
|レンブ
|renbu
|/
|-
|Mandarine
|[[w:ja:蜜柑|蜜柑]]
|みかん
|mikan
|
|-
|Mûre
|
|[[wikt:ブラックベリー|ブラックベリー]]
|burakkuberī
|Blackberry (en)
|-
|Myrtille
|
|[[w:ja:ブルーベリー|ブルーベリー]]
|burūberī
|Blueberry (en)
|-
|Noix de coco
|
|ココナッツ
|kokonattsu
|-
|Orange
|[[w:ja:橙|橙]]
|だいだい
|daidai
|オレンジ est beaucoup plus employé.
|-
|Pamplemousse
|
|グレープフルーツ
|gurēpufurūtsu
|Grapefruit (en)
|-
|Pastèque
|[[wikt:fr:西瓜|西瓜]]
|すいか
|suika
|
|-
|Pêche
|[[wikt:fr:桃|桃]]
|もも
|momo
|
|-
|Poire
|[[wikt:fr:梨|梨]]
|なし
|nashi
|
|-
|Pomelo
|[[wikt:fr:文旦|文旦]]
|ぶんたん
|buntan
|/
|-
|Pomme
|[[wikt:fr:林檎|林檎]]
|りんご
|ringo
|
|-
|Prune japonaise
|[[wikt:fr:梅|梅]]
|うめ
|ume
|
|-
|Raisin
|[[wikt:fr:葡萄|葡萄]]
|ぶどう
|budō
|-
|Tomate
|[[wikt:fr:蕃茄|蕃茄]]
|ばんか
|banka
|/ (On trouve également le terme 「トマト」issu de l'anglais "tomato".)
|}
== Boissons ==
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Boisson
|[[w:ja:飲み物|飲み物]]
|のみもの
|''nomimono''
|Boire/Chose
|-
|Café
|珈琲
|コーヒー
|''kōhī''
|coffee (en)
|-
|Eau
|水
|みず
|''mizu''
|eau
|-
|Jus
|汁
|しる / ジュース
|''jūsu''
|Juice (en)
|-
|Jus d'orange
|橙汁
|だいだいしる /
オレンジジュース
|''daidaishiru /''
''orenjijūsu''
|Orange juice (en)
|-
|Lait
|[[w:ja:牛乳|牛乳]]
|ぎゅうにゅう
|''gyūnyū''
|Vache/lait
|-
|Thé noir
|[[w:ja:紅茶|紅茶]]
|こうちゃ
|''kōcha''
|rouge/thé
|-
|Thé vert
|[[w:ja:御茶|御茶]]
|おちゃ
|''(o)cha''
|???/thé
|}
<nowiki>*</nowiki>''daidaishiru est aujourd'hui désuet et est remplacé par l'anglicisme'' ''orenjijūsu''
=== Alcool ===
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Alcool
|[[w:ja:酒|酒]]
|さけ<br>アルコール
|''sake''<br>''arukōru'' (en)
|
|-
|Bière
|[[wikt:麦酒|麦酒]]
|むぎさけ<br>ビール
|''mugisake''<br>''bīru''
|beer (en)
|-
|Bière pression
|[[wikt:生麦酒|生麦酒]]<br>[[w:ja:生ビール|生ビール]]
|なまむぎさけ<br>なまビール
|''namamugisake''<br>''namabīru''
|
|-
|Champagne
|
|シャンパン
|''shanpan''
|
|-
|Eau de vie
|
|ブランデー
|''burandē''
|
|-
|Gin
|
|ジン
|''jin''
|
|-
|Liqueur,alcool distillé
|[[w:ja:焼酎|焼酎]]
|しょうちゅう
|''shōchū''
|
|-
|Rhum
|
|ラム
|''ramu''
|
|-
|Saké
|[[w:ja:日本酒|日本酒]]
|にほんしゅ
|''nihonshu''
|Japon/Alcool
|-
|Scotch
|
|スコッチ
|''sukotchi''
|
|-
|Tequila
|
|テキーラ
|''tekīra''
|
|-
|Vin
|
|ワイン
|''wain''
|Wine (en)
|-
|Vin blanc
|[[w:ja:白ワイン|白ワイン]]
|しろワイン
|''shirowain''
|Blanc/Wine (en)
|-
|Vin rouge
|[[w:ja:赤ワイン|赤ワイン]]
|あかワイン
|''akawain''
|Rouge/Wine (en)
|-
|Vin de prune
|[[w:ja:梅酒|梅酒]]
|うめしゅ
|''umeshu''
|
|-
|Vodka
|
|ヴォッカ
|''vokka''
|
|-
|Whiskey
|
|ウィスキー
|''wisukī''
|
|}
==Plats typiques japonais==
*鹿尾菜の煮物 (hijiki no nimono) : algues noires bouillies.
*蒲焼き (kabayaki) : anguille grillée.
*肉じゃが ([[Livre de cuisine/Nikujaga|nikujaga]]) : bœuf ou porc, pommes de terre et oignons.
*串揚げ (kushiage) : brochettes panées de viande, de poisson ou de légumes.
*焼き鳥 (yakitori) : brochettes de poulet grillé.
*金平 (kin'pira) : carottes et bardanes râpées dans de la sauce soja.
*豚カツ (ton'katsu) : côtelettes de porc panées.
*コロッケ (korokke) : croquettes de pomme de terre.
*懐石料理 (kaisekiryōri) : dîner accompagnant la cérémonie du thé.
*鉄板焼き (teppan'yaki) : grillades de viande, légumes et poisson.
*野菜炒め (yasaītame) : légumes sautés.
*鋤焼き (sukiyaki) : marmite de viande et de légumes bouillis.
*饂飩 (udon) : nouilles à la farine de blé.
*蕎麦 (soba) : nouilles à la farine de sarrasin.
*天麩羅 (tenpura) : poisson et légumes frits.
*寿司 (sushi) : poisson cru sur du riz vinaigré.
*刺し身 (sashimi) : poisson cru en lamelles.
*御田 (oden) : pot-au-feu japonais.
*鳥の唐揚げ (tori no karāge) : poulet frit.
*茶碗蒸し (chawanmushi) : soupe crémeuse à base d'œuf.
*カレー (karē) : riz et [[Livre de cuisine/Curry japonais|curry]].
*豚汁 (ton'jiru) : soupe de porc au miso.
*お握り (onigiri) : sushi moulé à la main.
*拉麺 (rāmen) : nouilles accompagnées d'un bouillon à base de poisson ou de viande.
*付け麵 (tsukemen) : nouilles froides
*素麺 (sōmen) : nouilles japonaises très fines et blanches
*焼き蕎麦 (yakisoba) : nouilles sautées.
===Pâtisseries===
*霰 (arare) : biscuit de riz.
*花林糖 (karin'tō) : biscuit sucré à la farine de blé.
*御萩 (ohagi) : boulettes de riz et de pâte de haricots rouges sucrés.
*団子 (dan'go) : boulette de farine de riz.
*煎餅 (sen'bei) : galette de riz.
*海老煎餅 (ebisen'bei) : galette de riz aux crevettes.
*饅頭 (man'jū) : gâteaux fourrés à la pâte de haricot rouge.
*カステラ (kasutera) : gâteau mousseline.
*最中 (monaka) : gaufrettes fourrées à la pâte de haricot rouge.
*葛餅 (kuzumochi) : gelée de marante.
*大判焼き (ōban'yaki) : pancake chaud fourré à la pâte de haricot rouge.
*銅鑼焼き (dorayaki) : pancake froid fourré à la pâte de haricot rouge.
*羊羹 (yōkan) : pâte de haricot rouge gélifiée.
*餡パン (an'pan) : pain fourré à la pâte de haricot rouge.
*餅 (mochi) : gâteau de riz gluant.
*柏餅 (kashiwa-mochi) : gâteau de riz gluant fourré à la pâte de haricot rouge.
*メロンパン (meron'pan) : brioche fourrée.
==La cuisine chinoise et coréenne==
*焼き肉 (yakiniku) : barbecue à la coréenne
*キムチ (kimchi) : chou chinois pimenté à la coréenne
*餃子 (gyōza) : raviolis au porc et aux légumes
*焼売 (shūmai) : raviolis de porc à la vapeur
*ビビンバ (bibin'ba) : riz, œuf et légumes dans un bol
*炒飯 (chāhan) : riz sauté
*叉焼 (chāshū) : char siu
*春巻き (harumaki) : rouleau à l’œuf
*油条(yujō) : you tiao
==Plats occidentaux==
*グラタン (guratan) : gratin
*ハンバーガー (han'bāgā) : hamburger
*バナナスプリット (banana supuritto) : banana split
*ピザ (piza) : pizza
*フレンチトースト (Furenchi tōsuto) : pain perdu
*ポタージュ (potāju) : potage
*シチュー (shichū) : ragout
*チキンご飯 (chikin'gohan) : riz sauté avec du poulet
*チーズバーガー (chīzubāgā) : cheeseburger
*ステーキ (sutēki) : rumsteck
*サラダ (sarada) : salade
*サンドイッチ (san'doitchi (anciennement サンドヰッチ)) : sandwich
*スパゲティー (supagetī) : spaghettis
==Repas==
*朝食 (chōshoku) : petit déjeuner
*昼食 (chūshoku) : déjeuner
*軽食 (keishoku) : en-cas
*夕食 (yūshoku) : dîner
== Voir aussi ==
*[[wikt:Catégorie:Noms communs japonais|Les noms dans le wiktionnaire]]
{{Glossaires de Japonais}}
[[Catégorie:Glossaires de Japonais]]
[[en:Japanese/Vocabulary/Food and Drink]]
n6jlr5pu9kc26ne63uwf2rpqguqpb4g
772018
772017
2026-09-11T20:09:01Z
~2026-49266-35
124543
/* Alcool */
772018
wikitext
text/x-wiki
[[Image:Books-aj.svg aj ashton 01.svg|right|70px]]
==Nourriture==
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Avoine
|[[wikt:ja:烏麦|烏麦]]
|からすむぎ
|karasumugi
|
|-
|Baguette de pain
|
|バゲット
|bagetto
|
|-
|Blé
|[[wikt:ja:小麦|小麦]]
|こむぎ
|komugi
|-
|Bœuf
|[[w:ja:牛肉|牛肉]]
|ぎゅうにく
|gyūniku
|Vache/Viande
|-
|Carotte
|[[w:ja:人参|人参]]
|にんじん
|ninjin
|-
|Chou
|[[wikt:甘藍|甘藍]]<br>[[wikt:玉菜|玉菜]]
|かんらん<br>たまな<br>キャベツ
|kanran<br>tamana<br>kyabetsu
|-
|Courgette
|
|[[wikt:ズッキーニ|ズッキーニ]]
|zukkīni
|-
|Fruit
|[[w:ja:果物|果物]]
|くだもの
|kudamono
|
|-
|Légume
|[[w:ja:野菜|野菜]]
|やさい
|yasai
|
|-
|Navet
|[[w:ja:蕪|蕪]]
|かぶ
|kabu
|-
|Nourriture
|[[w:ja:食べ物|食べ物]]
|たべもの
|tabemono
|Manger/Chose
|-
|Oignon
|[[w:ja:玉葱|玉葱]]
|たまねぎ
|tamanegi
|-
|Œuf
|[[w:ja:卵|卵]]
|たまご
|tamago
|
|-
|Orge
|[[wikt:ja:大麦|大麦]]
|おおむぎ
|ōmugi
|-
|Pain
|[[w:ja:麺麭|麺麭]]
|パン
|pan
|
|-
|Patate douce
|[[wikt:ja:薩摩芋|薩摩芋]]
|さつまいも
|Satsuma imo
|Satsuma/pomme de terre (Satsuma était une ancienne province japonaise située sur l'île de Kyūshū, dans l'actuelle préfecture de Kagoshima.)
|-
|Poireau
|[[wikt:fr:葱|葱]]
|ねぎ
|negi
|
|-
|Poisson
|[[w:ja:魚|魚]]
|さかな
|sakana
|
|-
|Poivre
|[[w:ja:胡椒|胡椒]]
|こしょう
|koshō
|
|-
|Pois
|[[wikt:fr:豌豆|豌豆]]
|えんどう
|endō
|
|-
|Pomme d’amour
|[[wikt:fr:林檎飴|林檎飴]]
|りんごあめ
|ringoame
|Pomme/Bonbon
|-
|Pomme de terre
|[[wikt:fr:馬鈴薯|馬鈴薯]]
|じゃがいも
|jagaimo
|
|-
|Porc
|[[w:ja:豚肉|豚肉]]
|ぶたにく
|butaniku
|Porc/Viande
|-
|Poulet
|[[w:ja:鳥肉|鳥肉]]
|とりにく
|toriniku
|Oiseau/Viande
|-
|Radis blanc
|[[wikt:fr:大根|大根]]
|だいこん
|daikon
|Grand/Racine
|-
|Riz (non-cuisiné)
|[[w:ja:米|米]]
|こめ
|kome
|
|-
|Riz (cuisiné)
|[[w:ja:御飯|御飯]]
|ごはん
|gohan
|
|-
|Sarrasin
|[[wikt:ja:蕎麦|蕎麦]]
|そば
|soba
|-
|Sel
|[[w:ja:塩|塩]]
|しお
|shio
|
|-
|Soupe
|[[w:ja:汁物|汁物]]
|しるもの
|shirumono
|
|-
|Sucre
|[[w:ja:砂糖|砂糖]]
|さとう
|satō
|
|-
|Viande
|[[w:ja:肉|肉]]
|にく
|niku
|
|}
* Note : en japonais autochtone, il n'y a pas de distinction entre la soupe et le jus. Cependant, les anglicismes ジュース ("juice", pour désigner le jus) et スープ ("soup", pour désigner la soupe) sont largement employés aujourd'hui.
=== Fruits ===
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Abricot
|[[wikt:fr:杏子|杏子]]
|あんず
|anzu
|Abricot/Enfant
|-
|Ananas
|[[wikt:fr:鳳梨|鳳梨]]
|ほうり
|hōri
|/ (On trouve également le terme「パイナップル」issu de l'anglais "pineapple".)
|-
|Aubergine
|[[wikt:fr:茄子|茄子]]
|なす
|nasu
|
|-
|Banane
|[[wikt:fr:甘蕉|甘蕉]]
|かんしょう
|kanshō
|/ (On trouve également le terme 「バナナ」issu de l'anglais "banana".)
|-
|Cerise
|[[w:ja:桜ん坊|桜ん坊]]
|さくらんぼう
|sakuranbō
|/
|-
|Citron
|[[w:ja:檸檬|檸檬]]
|ねいもう
|neimō
|/ (On trouve également le terme「レモン」issu de l'anglais "lemon".)
|-
|Citrouille
|[[w:ja:南瓜|南瓜]]
|かぼちゃ
|kabocha
|
|-
|Figue
|[[w:ja:無花果|無花果]]
|いちじく
|ichijiku
|Sans/Fleur/Fruit
|-
|Fraise
|[[w:ja:苺|苺]]
|いちご
|ichigo
|
|-
|Framboise
|
|[[w:ja:ラズベリー|ラズベリー]]
|razuberī
|Raspberry (en)
|-
|Fruit
|[[wikt:fr:果物|果物]]
|くだもの
|kudamono
|Fruit/Chose
|-
|Jamalac
|[[wikt:fr:蓮霧|蓮霧]]
|レンブ
|renbu
|/
|-
|Mandarine
|[[w:ja:蜜柑|蜜柑]]
|みかん
|mikan
|
|-
|Mûre
|
|[[wikt:ブラックベリー|ブラックベリー]]
|burakkuberī
|Blackberry (en)
|-
|Myrtille
|
|[[w:ja:ブルーベリー|ブルーベリー]]
|burūberī
|Blueberry (en)
|-
|Noix de coco
|
|ココナッツ
|kokonattsu
|-
|Orange
|[[w:ja:橙|橙]]
|だいだい
|daidai
|オレンジ est beaucoup plus employé.
|-
|Pamplemousse
|
|グレープフルーツ
|gurēpufurūtsu
|Grapefruit (en)
|-
|Pastèque
|[[wikt:fr:西瓜|西瓜]]
|すいか
|suika
|
|-
|Pêche
|[[wikt:fr:桃|桃]]
|もも
|momo
|
|-
|Poire
|[[wikt:fr:梨|梨]]
|なし
|nashi
|
|-
|Pomelo
|[[wikt:fr:文旦|文旦]]
|ぶんたん
|buntan
|/
|-
|Pomme
|[[wikt:fr:林檎|林檎]]
|りんご
|ringo
|
|-
|Prune japonaise
|[[wikt:fr:梅|梅]]
|うめ
|ume
|
|-
|Raisin
|[[wikt:fr:葡萄|葡萄]]
|ぶどう
|budō
|-
|Tomate
|[[wikt:fr:蕃茄|蕃茄]]
|ばんか
|banka
|/ (On trouve également le terme 「トマト」issu de l'anglais "tomato".)
|}
== Boissons ==
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Boisson
|[[w:ja:飲み物|飲み物]]
|のみもの
|''nomimono''
|Boire/Chose
|-
|Café
|珈琲
|コーヒー
|''kōhī''
|coffee (en)
|-
|Eau
|水
|みず
|''mizu''
|eau
|-
|Jus
|汁
|しる / ジュース
|''jūsu''
|Juice (en)
|-
|Jus d'orange
|橙汁
|だいだいしる /
オレンジジュース
|''daidaishiru /''
''orenjijūsu''
|Orange juice (en)
|-
|Lait
|[[w:ja:牛乳|牛乳]]
|ぎゅうにゅう
|''gyūnyū''
|Vache/lait
|-
|Thé noir
|[[w:ja:紅茶|紅茶]]
|こうちゃ
|''kōcha''
|rouge/thé
|-
|Thé vert
|[[w:ja:御茶|御茶]]
|おちゃ
|''(o)cha''
|???/thé
|}
<nowiki>*</nowiki>''daidaishiru est aujourd'hui désuet et est remplacé par l'anglicisme'' ''orenjijūsu''
=== Alcool ===
{| {{tableau japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
! Sens littéral
|-
|Alcool
|[[w:ja:酒|酒]]
|さけ<br>アルコール
|''sake''<br>''arukōru'' (en)
|
|-
|Bière
|[[wikt:麦酒|麦酒]]
|むぎさけ<br>ビール
|''mugisake''<br>''bīru''
|beer (en)
|-
|Bière pression
|[[wikt:生麦酒|生麦酒]]<br>[[w:ja:生ビール|生ビール]]
|なまむぎさけ<br>なまビール
|''namamugisake''<br>''namabīru''
|
|-
|Champagne
|
|シャンパン
|''shanpan''
|
|-
|Eau de vie
|
|ブランデー
|''burandē''
|
|-
|Gin
|
|ジン
|''jin''
|
|-
|Liqueur, alcool distillé
|[[w:ja:焼酎|焼酎]]
|しょうちゅう
|''shōchū''
|
|-
|Rhum
|
|ラム
|''ramu''
|
|-
|Saké
|[[w:ja:日本酒|日本酒]]
|にほんしゅ
|''nihonshu''
|Japon/Alcool
|-
|Scotch
|
|スコッチ
|''sukotchi''
|
|-
|Tequila
|
|テキーラ
|''tekīra''
|
|-
|Vin
|
|ワイン
|''wain''
|Wine (en)
|-
|Vin blanc
|[[w:ja:白ワイン|白ワイン]]
|しろワイン
|''shirowain''
|Blanc/Wine (en)
|-
|Vin rouge
|[[w:ja:赤ワイン|赤ワイン]]
|あかワイン
|''akawain''
|Rouge/Wine (en)
|-
|Vin de prune
|[[w:ja:梅酒|梅酒]]
|うめしゅ
|''umeshu''
|
|-
|Vodka
|
|ヴォッカ
|''vokka''
|
|-
|Whiskey
|
|ウィスキー
|''wisukī''
|
|}
==Plats typiques japonais==
*鹿尾菜の煮物 (hijiki no nimono) : algues noires bouillies.
*蒲焼き (kabayaki) : anguille grillée.
*肉じゃが ([[Livre de cuisine/Nikujaga|nikujaga]]) : bœuf ou porc, pommes de terre et oignons.
*串揚げ (kushiage) : brochettes panées de viande, de poisson ou de légumes.
*焼き鳥 (yakitori) : brochettes de poulet grillé.
*金平 (kin'pira) : carottes et bardanes râpées dans de la sauce soja.
*豚カツ (ton'katsu) : côtelettes de porc panées.
*コロッケ (korokke) : croquettes de pomme de terre.
*懐石料理 (kaisekiryōri) : dîner accompagnant la cérémonie du thé.
*鉄板焼き (teppan'yaki) : grillades de viande, légumes et poisson.
*野菜炒め (yasaītame) : légumes sautés.
*鋤焼き (sukiyaki) : marmite de viande et de légumes bouillis.
*饂飩 (udon) : nouilles à la farine de blé.
*蕎麦 (soba) : nouilles à la farine de sarrasin.
*天麩羅 (tenpura) : poisson et légumes frits.
*寿司 (sushi) : poisson cru sur du riz vinaigré.
*刺し身 (sashimi) : poisson cru en lamelles.
*御田 (oden) : pot-au-feu japonais.
*鳥の唐揚げ (tori no karāge) : poulet frit.
*茶碗蒸し (chawanmushi) : soupe crémeuse à base d'œuf.
*カレー (karē) : riz et [[Livre de cuisine/Curry japonais|curry]].
*豚汁 (ton'jiru) : soupe de porc au miso.
*お握り (onigiri) : sushi moulé à la main.
*拉麺 (rāmen) : nouilles accompagnées d'un bouillon à base de poisson ou de viande.
*付け麵 (tsukemen) : nouilles froides
*素麺 (sōmen) : nouilles japonaises très fines et blanches
*焼き蕎麦 (yakisoba) : nouilles sautées.
===Pâtisseries===
*霰 (arare) : biscuit de riz.
*花林糖 (karin'tō) : biscuit sucré à la farine de blé.
*御萩 (ohagi) : boulettes de riz et de pâte de haricots rouges sucrés.
*団子 (dan'go) : boulette de farine de riz.
*煎餅 (sen'bei) : galette de riz.
*海老煎餅 (ebisen'bei) : galette de riz aux crevettes.
*饅頭 (man'jū) : gâteaux fourrés à la pâte de haricot rouge.
*カステラ (kasutera) : gâteau mousseline.
*最中 (monaka) : gaufrettes fourrées à la pâte de haricot rouge.
*葛餅 (kuzumochi) : gelée de marante.
*大判焼き (ōban'yaki) : pancake chaud fourré à la pâte de haricot rouge.
*銅鑼焼き (dorayaki) : pancake froid fourré à la pâte de haricot rouge.
*羊羹 (yōkan) : pâte de haricot rouge gélifiée.
*餡パン (an'pan) : pain fourré à la pâte de haricot rouge.
*餅 (mochi) : gâteau de riz gluant.
*柏餅 (kashiwa-mochi) : gâteau de riz gluant fourré à la pâte de haricot rouge.
*メロンパン (meron'pan) : brioche fourrée.
==La cuisine chinoise et coréenne==
*焼き肉 (yakiniku) : barbecue à la coréenne
*キムチ (kimchi) : chou chinois pimenté à la coréenne
*餃子 (gyōza) : raviolis au porc et aux légumes
*焼売 (shūmai) : raviolis de porc à la vapeur
*ビビンバ (bibin'ba) : riz, œuf et légumes dans un bol
*炒飯 (chāhan) : riz sauté
*叉焼 (chāshū) : char siu
*春巻き (harumaki) : rouleau à l’œuf
*油条(yujō) : you tiao
==Plats occidentaux==
*グラタン (guratan) : gratin
*ハンバーガー (han'bāgā) : hamburger
*バナナスプリット (banana supuritto) : banana split
*ピザ (piza) : pizza
*フレンチトースト (Furenchi tōsuto) : pain perdu
*ポタージュ (potāju) : potage
*シチュー (shichū) : ragout
*チキンご飯 (chikin'gohan) : riz sauté avec du poulet
*チーズバーガー (chīzubāgā) : cheeseburger
*ステーキ (sutēki) : rumsteck
*サラダ (sarada) : salade
*サンドイッチ (san'doitchi (anciennement サンドヰッチ)) : sandwich
*スパゲティー (supagetī) : spaghettis
==Repas==
*朝食 (chōshoku) : petit déjeuner
*昼食 (chūshoku) : déjeuner
*軽食 (keishoku) : en-cas
*夕食 (yūshoku) : dîner
== Voir aussi ==
*[[wikt:Catégorie:Noms communs japonais|Les noms dans le wiktionnaire]]
{{Glossaires de Japonais}}
[[Catégorie:Glossaires de Japonais]]
[[en:Japanese/Vocabulary/Food and Drink]]
tvwdgln8nd2ryqmlli2o9jtiv12y8vi
Discussion utilisateur:Cdang
3
7413
771985
641936
2026-09-11T15:01:08Z
Xhungab
23827
771985
wikitext
text/x-wiki
Merci de me contacter sur [[Wikipedia:fr:Discussion Utilisateur:cdang]].
== Convention de nommage ==
Bonjour,
J'ai vu que tu as renommé une page en retirant le slash [http://fr.wikibooks.org/w/index.php?title=Mod%C3%A8le:Cours_rpg_maker_xp&curid=13173&diff=81806&oldid=81803]
En fait, on s'est rendu compte qu'il était beaucoup plus pratique d'adopter le modèle Livre/chapitre plutot que Livre_chapitre car ca permet de lever les ambiguïtés (sur le lieu de la coupure) et cela permet d'établir une arborescence automatique avec un lien renvoyant vers l'article-père en dessous du titre des sous-pages. Le cas de [[Calcul tensoriel/Notions élémentaires/Tenseur métrique/Pseudo-contraction de la dérivée partielle/Démonstration|cet article]] (abusif certes) montre l'intérêt de la chose. La [[Wikilivres:Conventions sur les titres d'articles|recommandation]] a d'ailleurs été actualisée en ce sens. Amitiés. [[Utilisateur:J.M. Tavernier|J.M. Tavernier]] 13 décembre 2006 à 16:38 (CET)
== À propos de [[Utilisateur:Cdang/projet|Grôle]] ==
Salut, j'ai viens de lire la page sur Grôle et je suis heureux de constater que quelqu'un ait eu une idée qui me trote dans le tête depuis un bout de temps. J'ai déjà mené plusieurs réflexions sur ce sujet qui pourrait être une contribution. Serais-tu disponible sur [[w:jabber|jabber]] pour un brainstorming ? [[Utilisateur:Sub|Sub]] 24 décembre 2006 à 13:51 (CET)
:Je ne parlais pas de se contacter maintenant mais plus tard évidemment (janvier). En fait je penses pouvoir synthétiser mes réflexions par écrit. Je te les communiquerai en temps voulu. Personnelement bien qu'ayant des idées, je ne penses pas contribuer de si tôt : j'ai déjà pas mal de projets à finir. Bon courage :) [[Utilisateur:Sub|Sub]] 24 décembre 2006 à 14:28 (CET)
----
Suite à ta demande du 18 Janvier, je te propose un premier "patch", comprenant mes suggestions, remarques, corrections et propositions. Je l'ai placé en [[Discussion Utilisateur:Cdang/projet#Permier "patch" (Sub)|page de discussion du projet]]. j'espère que ce premier jet te satisfera et t'encouragera à continuer. [[Utilisateur:Sub|Sub]] 22 janvier 2007 à 17:15 (CET)
== Artefacts, projet de JdR libre ==
Un auteur semble se lancer dans la création d'un JdR libre (licence cc by-nc) appellé [http://www.rpgbox.com/wiki/doku.php?id=Artefacts Artefacts]. Je suis tombé dessus par hasard et j'ai pensé que tu souhaiterai en être informé. [[Utilisateur:Sub|Sub]] 2 février 2007 à 17:08 (CET)
== Avenir de Grôle ==
J'ai d'autres idées plus ambitieuse pour l'avenir de grôle. Si tu le met en pdf sur une « page perso », il risque d'être un énième JdR gratuit téléchargeable comme BaSIC, FRAPS etc... Que penserais-tu d'ouvrir un site dédié sur un hébergeur libre et gratuit ([tuxfamily.org] ?) dédié à grôle, ses univers, ses modules, sa communauté et à plus long terme, ses logiciels de création/génération de persos etc... Je penses qu'un projet d'une telle envergure et aussi novateur suffit à justifier la création d'un site dédié. Celà ne couterait rien en argent, très peu en compétences (installation basique de MediaWiki), reste le temps à consacrer à la création du contenu. Sur ce point, je penses qu'il sera facile de ralier le créateur d'Artefacts, bien en mal de participation et d'idées, à notre cause. Personnelement, je ne me lancerai pas là dedans seul, mais je suis près à fournir du contenu, au moins au début, dans la limite de ma persistance déjà mise à rude épreuve sur wikibooks... [[Utilisateur:Sub|Sub]] 7 février 2007 à 13:02 (CET)
:Je suis réticent à parler de fusion, il n'a pratiquement aucun contenu et en plus le peu qui est présenté sur le site est sous une licence incompatible avec la GFDL que je considère comme étant la plus pertinente pour ce projet. De plus, son site est mixé avec un projet de système de jeu en ligne un peu obscur. Je penses que nous devrions suivre notre propre chemin. Enfin, j'ai suivi l'ensemble de ta contribution sur wikilivres, l'assiduité, la finition et la qualité de tes travaux. En fait, je penses que t'as les épaules et je préfère te voir à la tête de ce projet. [[Utilisateur:Sub|Sub]] 7 février 2007 à 22:50 (CET)
::Je viens aux nouvelles ;). Quoi de neuf ? [[Utilisateur:Sub|Sub]] 22 février 2007 à 19:36 (CET)
:::Bon, j'attend que tu sois disponible, je préfère y être à deux. Ça me laissera le temps de finir quelques projets en cours et de tester les règles de combat dont certains bonus me semblent douteux ;-). Si besoin, j'essaierai de te proposer un second patch, du même genre que le premier (j'espère que la forme te convient...). [[Utilisateur:Sub|Sub]] 23 février 2007 à 14:26 (CET)
== [[Programmation HTML Introduction|Programmation HTML]] ==
Bonjour,
Je vois que tu sembles contribuer activement (et peut être un peu solitairement ;-) ) au projet [[Programmation HTML Introduction|Programmation HTML]]. Le contenu à ce jour est déjà conséquent (bravo ! ;-) ). Cependant, il comporte, me semble-t-il, un certain nombre d'erreurs ou de ''mauvaises pistes'' peu rigoureuses, qui engagent dans des directions problématiques. Je me suis permis de laisser dans les pages de discussions différentes remarques et propositions de correction (très rapidement rédigées, et sans doute un peu abruptement, je l'avoue).
Le potentiel de ce livre me semble très intéressant, notamment pour répondre à la quasi absence actuelle de didacticiel à jour, exact et en même temps ''accessible au débutant''. C'est pourquoi j'y apporterai volontiers ma participation (étant moi-même notament formateur dans ce domaine).
Au plaisir de te lire à ce sujet <small>(tu peux répondre ici, je suis cette page de discussion, ainsi que les pages de discussion de Programmation HTML)</small>.
Amidalement, --[[Utilisateur:LgD]] 15 mars 2007 à 12:13 (CET)
== "implémenté" ==
Bonjour,<br />Attention aux remplacements du terme "implémenté". Il n'est certes pas du meileur français classique qui soit, mais il a un sens précis (dans le cas qui occupe, le degré et la qualité de prise en charge d'une propriété CSS et ses valeurs par un navigateur). Eventuellement, "supporté" peut être un substitut correct, selon le contexte, mais la suppression systématique d'implémenté ne me semble pas judicieuse ;) .Cordialement, --[[Utilisateur:LgD|LgD]] 23 mars 2007 à 15:29 (CET)
:oups, oubli: je me suis permis de reverter [http://fr.wikibooks.org/w/index.php?title=Programmation_CSS/Introduction&curid=2271&diff=107512&oldid=107509 ici] faute de mieux pour l'instant, car le remplacement par "développé" induisait un tout autre sens.
::ne nous énervons pas, il ne s'agissait que d'un retour ''temporaire'' au terme contesté, pour éviter un contre-sens dans ce passage précis, qui nécessite en effet d'être réécrit ;) --[[Utilisateur:LgD|LgD]] 23 mars 2007 à 15:50 (CET)
:::Rassures-toi, je n'interviendrais pas ici si je ne m'étais pas fixé pour règle d'être aussi imperméable que possible aux [http://fr.wikibooks.org/w/index.php?title=Discuter%3AProgrammation_CSS%2FTechniques_avanc%C3%A9es&diff=107183&oldid=107174 anecdotes conflituelles] ;)<br><humour>Tiens, une solution: je t'abandonne ''implémentation'' et tu m'abandonnes l'''adresse réticulaire'' ?? ;)</humour><br>Plus sérieusement, j'utilise couramment ''implémentation'' comme dans mes travaux techniques. Cela dit, je suis tout à fait d'accord pour son remplacement par des termes plus courants, voire par l'exact ''implantation''. Le souci est juste de ne pas fausser le sens technique. Il suffit pour cela d'en discuter si nécessaire au cas par cas. Dans le cas précis de la page concernée, ma rédaction trop rapide était effectivement exécrable, je vais la reprendre. Amicalement, --[[Utilisateur:LgD|LgD]] 25 mars 2007 à 13:38 (CEST)
== Ton avis sur wikibooks et l'interaction avec la wikiversité ==
Pourrais-tu donner ton opinion sur [[Wikilivres:Le Bistro#distinction entre Wikibooks et Wikiversité|la conversation sur le Bistro]] ? Ton avis peut être pertinent étant donné que tu as beaucoup contribué. Merci [[Utilisateur:Sub|Sub]] 24 mai 2007 à 17:42 (CEST)
== Trouvaille ==
Dans notre foutoir de wiki, on trouve quelques trucs perdus... Je suis tombé sur [[Guide pour jeux de rôles]]. Je l'ai rangé dans [[:Catégorie:Jeu de rôle]], créée pour l'occasion. Le seul contributeur est incatif depuis 2006, y compris sur wikipédia. J'ai pensé que ça pouvait t'intéresser de savoir qu'on avait ça... [[Utilisateur:Sub|Sub]] 30 mai 2007 à 15:48 (CEST)
== Merci ! ==
Pour ton gros travail sur [[Programmation LaTeX]], qui m'est fort utile ! (et surement à beaucoup d'autres). [[Utilisateur:Tognopop|Tognopop]] 7 août 2007 à 16:45 (CEST)
== Coloration syntaxique ==
Bonjour. En passant je te signale un outil qui pourrait t'être utile pour donner des exemples de code (latex, ...). Tout est indiqué dans la [[v:Aide:Coloration syntaxique|Wikiversité]]. --[[Utilisateur:Djiboun|Djiboun]] 7 août 2007 à 19:27 (CEST)
== Retour de ligne dans les modèles ==
<s>Je ne l'avais découvert qu'hier sur Wikipédia. Cela ne me dérange pas de corriger, mais pourquoi ne peut-il y avoir de retour à la ligne avant <nowiki><noinclude></nowiki> ?</s>
[[Utilisateur:Esope|Esope]] 12 septembre 2007 à 14:35 (CEST)
:Je n'ai rien dit, je viens de comprendre. [[Utilisateur:Esope|Esope]] 12 septembre 2007 à 14:36 (CEST)
== Ton témoignage ==
J'ai lancé un [[Discuter:Wikilivres|appel à témoignage]], tu es concerné ! J'espère que tu participeras parce que tes idées devraient être intéressantes vu la qualité de tes travaux. Tu expliqueras comment tu écris aussi vite des contenus aussi bon ;) [[Utilisateur:Sub|Sub]] 6 juillet 2008 à 15:50 (CEST)
==Livre sur LATeX==
Bonjour,
Je cherche une information très précise sur LATeX que ne donne pas le livre que tu as rédigé. Je pose donc la question directement à l'auteur {{sourire}} Connais tu une fonction sur LATeX qui envoie True si le compteur Machin est une chiffre (1, 2, 3, ...) et non une lettre (A, B, C, ...) ?
[[Utilisateur:Octozor|Octozor]] 5 novembre 2008 à 00:57 (CET)
== Écrivons notre histoire ==
Comme tu es un ancien de Wikilivres, je te propose de relater tes souvenirs dans l'[[Wikilivres:Historique]]. [[Utilisateur:Sub|Sub]] 15 décembre 2008 à 19:23 (CET)
== Copyvio dans [[LaTeX]] ? ==
Pourrais-tu [[Discuter:Programmation LaTeX#Copyvio dans un chapitre ?|donner ton avis]] ? Je ne sais pas dans quel sens s'est fait la copie. [[Utilisateur:Sub|Sub]] 15 décembre 2008 à 19:25 (CET)
== Patroller ==
{{Boîte déroulante|titre=Proposition patrouilleur|contenu=
Bonjour,
Tu es manifestement un contributeur confirmé et en tant que tel tu n'apprécies pas que ton travail soit détérioré par des vandales.
Globalement tu manifestes un intérêt pour ce projet et sa détérioration te contrarie.
Alors, ce qui suit doit t'intéresser.
Comme tu le sais peut-être, le projet Wikilivres est doté maintenant de [[Wikilivres:Patrouilleur|nouveaux outils]] à utiliser pour lutter contre le vandalisme.
Tu pourrais aider à la préservation du contenu de Wikilivres de façon active ou de façon passive.
Comment ?
En donnant ton accord pour être '''patrouilleur''' dans ta page de discussion (une proposition de candidature serait alors ouverte à ton nom) ou en posant ta candidature directement sur la [[Wikilivres:Patrouilleur|page spéciale]]. Un vote aurait lieu et comme dans tout vote il ne peut pas être présagé du résultat. Cependant le fait que tu reçoives ce message sur ta page de discussion est un signe très favorable qui doit te permettre de prendre ta décision avec sérénité.
En étant patrouilleur, tu aurais un statut qui ne te donne aucune obligation mais seulement des droits.
Tu serais utile au projet
*passivement (tes contributions ne seraient pas précédées sur la page de modifications récentes par le signe {{Coloré|red|'''!'''}} et de ce fait tes contributions ne figureraient pas dans les contributions à contrôler visibles par les autres patrouilleurs)
*activement, si tu le désirais, en marquant comme contrôlées par rapport au vandalisme les pages de modifications portant des signes {{Coloré|red|'''!'''}}
Sache que tu es totalement libre de ne pas répondre à cette proposition, ni par Oui, ni par Non d’ailleurs. Ce n’est absolument pas une obligation morale et chacun est libre de faire ce qu’il veut.
''Pour répondre éventuellement, merci d’ajouter ci-dessous une des mentions suivantes et de signer.
*Oui, je suis d'accord pour avoir le statut de patrouilleur.
*Non, je ne suis pas d'accord pour avoir le statut de patrouilleur.
}}[[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<font color="#FF6600">$</font>]]) 21 mai 2010 à 15:48 (CEST)
== Suppression de [[Technologie/Matériaux/Généraliités/Désignation normalisée des aciers|Désignation normalisé des aciers]] ==
Bonjour {{PAGENAME}},<br />
Vous mettez en commentaire ''blanchi pour la suppression'', est-ce que vous voulez la supprimer, car il n'y a aucune demande de suppression pour cette page. Merci<br />
[[Utilisateur:TouzaxA|TouzaxA]], [[Discussion Utilisateur:TouzaxA|Discuter]].[[Image:Blender.png|15px|lien=Blender, Quézaco ?|Voir Blender, Quézaco ?, le tutoriel sur Blender 2.57]] Le 26 mai 2011 à 16:16 (CEST)
:{{fait}} [[Utilisateur:TouzaxA|TouzaxA]], [[Discussion Utilisateur:TouzaxA|Discuter]].[[Image:Blender.png|19px|lien=Blender, Quézaco ?|Voir Blender, Quézaco ?, le tutoriel sur Blender 2.57]] Le 1 juin 2011 à 13:27 (CEST)
== [[Cristallographie géométrique]] ==
Salut,
je vois que tu passes dans le coin... si ça t'intéresse, je serais ravie de tes contributions ou commentaires sur les pages de ce livre. Amicalement, [[Utilisateur:Perditax|Perditax]] 8 décembre 2011 à 10:05 (CET)
== Documents ==
Salut Christophe,
Voici quelques mois, je t'avais proposé une importante collection de documents de technologie susceptibles de te fournir une aide pédagogique importante. Entre temps il s'est passé pas mal de choses, parmi lesquelles l'organisation de deux salons photo internationaux à Pessac et Bordeaux.
Es-tu toujours d'accord pour recevoir ces documents ? Si oui, il faudrait que tu me donnes ton adresse, je peux probablement m'arranger pour te les porter directement.
Amitiés,
[[Utilisateur:Jean-Jacques MILAN|Jean-Jacques MILAN]] ([[Discussion utilisateur:Jean-Jacques MILAN|discussion]]) 26 juin 2012 à 14:09 (CEST)
== Cdang, vous aussi venez discuter de l'avenir du mouvement ==
Bonjour,
Je viens vous inviter à rejoindre les discussions en cours sur le futur du mouvement Wikimédia.
Depuis quelques jours déjà, des contributeurs, à travers divers projets et langues sur wikis, discutent de nos priorités, en tant que mouvement, pour les 15 années à venir : Quelle est la chose la plus importante à faire ? Comment maintenir ou accélérer notre progrès? Qu'est-ce qui pourrait consolider notre unité en tant que mouvement? Voici autant de questions auxquelles tous essaient d’apporter des réponses.
Vous aussi, votre point de vue compte énormement. On prend l'avis de tout le monde y compris les correcteurs d'orthographe {{sourire}}.
N’hésitez surtout pas à consulter la page des discussions sur [[Wikilivres:Mouvement Wikimedia/Stratégie 2017]] pour y laisser vos propositions ou pour débattre des propositions des autres.
Voici quelques liens utiles sur le contexte et le processus de cette consultation :
* [[M:Strategy/Wikimedia movement/2017/Process/Briefing/fr|Contexte général]], pour comprendre les objectifs et jeter un coup d'oeil aux études et statistiques connexes
* [[M:Strategy/Wikimedia movement/2017/Process/fr|Calendrier et processus]], pour plus de détails sur les dates et consulter le graphique illustrant toutes les étapes
* [[M:Strategy/Wikimedia_movement/2017/Frequently_asked_questions/fr|Foire Aux Questions (FAQ)]], pour les questions qui reviennent fréquemment
N'hésitez pas à me contacter au besoin. Amicalement, [[Utilisateur:Samuel (WMF)|Samuel (WMF)]] ([[Discussion utilisateur:Samuel (WMF)|discussion]]) 29 mars 2017 à 17:34 (CEST)
== Livre pour la vitrine ? ==
Bonjour Cdang,
Le livre « [[Jeu de rôle sur table — Jouer, créer]] » semble complet et prêt pour [[Wikilivres:Vitrine|la vitrine]].
Un aperçu :
<div>{{Wikilivres:Vitrine/Jeu de rôle sur table — Jouer, créer}}</div>{{clr}}
-- ◄ [[Utilisateur:DavidL|'''D'''avid '''L''']] • [[Discussion Utilisateur:DavidL|discuter]] ► 23 mai 2020 à 16:21 (CEST)
:{{notif|DavidL}} Complet certainement pas, mais il est bien avancé ^_^ Merci pour cette mise en lumière.
:[[Utilisateur:Cdang|Cdang]] ([[Discussion utilisateur:Cdang|discussion]]) 30 mai 2020 à 08:37 (CEST)
::La présentation est prête, mais pas encore mise en place dans [[Wikilivres:Vitrine]] ni dans le cycle des livres présentés en [[Accueil|page d'accueil]], pour te laisser le temps d'avancer davantage ou revoir la présentation du livre.
::-- ◄ [[Utilisateur:DavidL|'''D'''avid '''L''']] • [[Discussion Utilisateur:DavidL|discuter]] ► 30 mai 2020 à 11:01 (CEST)
== Demandes de prises de décision ==
Bonjour, Pour info.
J'ai posé deux demandes de prises de décisions concernant le sujet suivant.
* '''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Cr%C3%A9er_un_espace_lecteurs_et_un_espace_auteurs_sur_la_page_d%27accueil Créer un espace lecteurs et un espace Les coulisses de Wikibook]'''
* '''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]'''
Merci [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 17:00 (CEST)
5d0eq012ycz2ujvi5e1g7dysxv1eyi5
Japonais/Grammaire/Suffixe modal
0
9671
772012
732506
2026-09-11T19:53:23Z
~2026-49266-35
124543
772012
wikitext
text/x-wiki
{{niveau élémentaire}}
Les ''suffixes modaux'' désignent des mots modifiant le sens du verbe qu'ils suivent. Ces suffixes sont suivis de です ou だ, marquant la politesse, les verbes sont à la forme neutre.
'''Exemple :''' 私は日本へ行くつもりです (watashi ha nihon he iku tsumori desu) = J'ai l'intention d'aller au Japon.
{| {{tableau_japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
|-
| Avant de…
| [[wiktionary:fr:前|前]]
| まえ
| mae
|-
| Avoir l'intention de…
| [[wiktionary:fr:積もり|積もり]]
| つもり
| tsumori
|-
| Devoir (obligation morale)
| [[wiktionary:fr:可き|可き]]
| べき
| beki
|-
| En train de…
| [[wiktionary:fr:途中|途中]]
| とちゅう
| tochū
|-
| Projet de…
| [[wiktionary:fr:予定|予定]]
| よてい
| yotei
|}
{{Grammaire Japonaise}}
p9avfqi91r3i40j27yoodo9v58219pi
772013
772012
2026-09-11T19:55:32Z
~2026-49266-35
124543
772013
wikitext
text/x-wiki
{{niveau élémentaire}}
Les ''suffixes modaux'' désignent des mots modifiant le sens du verbe qu'ils suivent. Ces suffixes sont suivis de です ou だ, marquant la politesse, les verbes sont à la forme neutre.
'''Exemple :''' 私は日本へ行くつもりです。 (Watashi wa Nihon he iku tsumori desu.) = J'ai l'intention d'aller au Japon.
{| {{tableau_japonais}}
! Français
! [[Japonais/Kanji|Kanji]]
! [[Japonais/Kana|Kana]]
! [[Japonais/Romaji|Rōmaji]]
|-
| Avant de…
| [[wiktionary:fr:前|前]]
| まえ
| mae
|-
| Avoir l'intention de…
| [[wiktionary:fr:積もり|積もり]]
| つもり
| tsumori
|-
| Devoir (obligation morale)
| [[wiktionary:fr:可き|可き]]
| べき
| beki
|-
| En train de…
| [[wiktionary:fr:途中|途中]]
| とちゅう
| tochū
|-
| Projet de…
| [[wiktionary:fr:予定|予定]]
| よてい
| yotei
|}
{{Grammaire Japonaise}}
59ho1386conj46xuq30xrngvzznh8k6
Livre de cuisine/Compote de pommes
0
10458
771986
665941
2026-09-11T17:18:40Z
Cdang
1202
defaultsort ; +recettes à base de compote
771986
wikitext
text/x-wiki
{{SansGluten}}
{{Livre de cuisine}}
== Ingrédients ==
* 20 pommes fraîches,
* 1 tasse de {{i|sucre}}.
== Ustensiles spécifiques ==
* outil pour peler une pomme,
* mélangeur,
* gros {{Ustensile|chaudron}}.
== Préparation ==
# Peler les pommes et les déposer dans un grand bol d'[[w:eau|eau]] pour qu'elles ne brunissent pas.
# Mettre les {{i|pomme}}s coupées en quatre dans un gros chaudron sur le rond à médium pendant une demi-heure.
# Remuer toutes les deux minutes.
# Vérifier que les pommes soient ramollies.
# Fermer le rond et mettre les pommes par petits groupes dans le mélangeur.
# Quand la [[wikt:texture|texture]] est [[wikt:uniforme|uniforme]], retirer les pommes.
# Réfrigérer.
{{DEFAULTSORT:compote de pommes}}
[[Catégorie:Desserts]]
[[Catégorie:Recettes végétaliennes]]
[[Catégorie:Recettes végétariennes]]
[[Catégorie:Recettes de cuisine à base de compote|pommes, compotes de]]
sbyzkznpt4a0yzvci6dzw6jtw1pk3wj
Discussion utilisateur:Mewtow
3
55231
771983
760883
2026-09-11T14:53:44Z
Xhungab
23827
771983
wikitext
text/x-wiki
{| width="100%" align="center" style="display:block; float:center; background:#FFF; text-size:100%; margin:.5 .5 .5 .5em; padding:2px; border:1px solid #aaa; background-color:transparent;"
|----
| colspan="2" align="center" width="95%" style="height:9px; margin:.2em; padding:5px; border:1px solid #aaa; background-color:#bfbfff; font-size:130%; text-align:center; float:center" |
'''Bienvenue sur Wikilivres, {{BASEPAGENAME}} !'''
|----
| colspan="2" align="center" width="85%" style="font-size:110%" |
<br/>Wikilivres est un projet francophone de rédactions de manuels pédagogiques libres.
|----
| valign="middle" align="center" | [[Image:Crystal Clear app Login Manager.png|55px|lien=]]
| align="left" width="90%" style="font-size:100%; margin:.7em; padding:4px;" |
Si vous désirez vous investir dans ce projet passionnant, vous êtes le bienvenu. Tous les contributeurs de Wikilivres vous invitent à corriger et développer les livres existants et à créer le votre !<br />
Prenez avant toute chose connaissance des '''[[Wikilivres:Présentation|principes fondateurs de Wikilivres]]''' à la base du projet.
N'ayez aucune crainte d'abîmer la bibliothèque : toutes les modifications sont suivies par des contributeurs plus expérimentés qui pourront corriger vos éventuelles erreurs. Ne vous offensez pas de ces interventions ni des messages destinés à vous aider à comprendre le projet et ses règles. Suivez leurs conseils et n'hésitez pas à demander simplement plus d'explications !
|----
| valign="middle" align="center" | [[Image:ACE.png|55px|lien=]]
| align="left" width="90%" style="font-size:100%; margin:.7em; padding:4px;" |
Lorsque vous contribuez, '''gardez à l'esprit que Wikilivres est une ''bibliothèque universelle''''', il faut donc veiller à '''respecter un point de vue vérifiable'''. De plus, le contenu doit rester conforme à notre licence d'utilisation libre (GFDL).<br>
Les utilisateurs travaillent en harmonie et privilégient la discussion aussi courtoise que possible pour régler les oppositions, aussi, prenez le temps de vous y habituer. Pensez à vous présenter également sur votre [[Utilisateur:{{PAGENAME}}|page d'utilisateur]], ce qui permettra à chacun de connaître vos centres d'intérêt et de mieux vous guider vers les divers projets thématiques. Pour cela, vous pouvez employer les [[Wikilivres:Boîtes utilisateur|boîtes utilisateur]].<br />
Bien qu'il s'agisse d'un travail de rédaction complexe résultant de l'action de milliers de contributeurs francophones du monde entier, sa philosophie peut être résumée en quelques mots : '''« N'hésitez pas à l'améliorer ! »'''
|----
| valign="middle" align="center" | [[Image:Plume pen w.png|55px|lien=]]
| align="left" width="90%" style="font-size:100%; margin:.7em; padding:4px;" |
Au cours d'une discussion, n'oubliez pas de signer vos messages, à l'aide de quatre tildes (<nowiki>~~~~</nowiki>) ou du bouton [[Image:Bouton Signature.png|25px|lien=]] présent en haut de la fenêtre de modification. En revanche ne signez pas les livres lorsque vous en créez ou que vous les modifiez car l'historique permet de retrouver toutes vos contributions.<br>
|----
| valign="middle" align="center" | [[Image:Wikibooks logo.svg|55px|lien=]]
| align="left" width="90%" style="font-size:100%; margin:.7em; padding:4px;" |
Vous pouvez trouver des explications détaillées à partir des pages :
* '''''[[Aide:Accueil|Tout l'indispensable]]''''', à lire absolument,
* '''[[Aide:Syntaxe|Syntaxe]]''', les conventions d'écriture à connaître.
* '''[[Aide:Poser une question|Poser une question]]''', si vous vous sentez perdu.
* '''[[Aide:Comment modifier une page|Comment modifier une page]]'''
Vous pourrez ajouter par la suite d'autres pages d'aide ou les informations dont vous pensez avoir besoin dans votre [[Utilisateur:{{PAGENAME}}|espace utilisateur]].
Si vous souhaitez contribuer mais que vous n'avez pas d'idées, allez faire un tour sur [[Wikilivres:Demande de livre|les demandes de livres et de traductions !]].
Bonne contribution, et n'hésitez pas à me contacter si besoin !<br>
<small>PS: If you arn't French, you can consult the [[Wikilivres:Ambassade|embassy]] for help.</small>
|}
-- ◄ [[Utilisateur:DavidL|'''D'''avid '''L''']] • [[Discussion Utilisateur:DavidL|discuter]] ► 9 mars 2013 à 20:42 (CET)
== Page et sous-pages ==
Bonjour,
Remarque concernant l'organisation d'un livre : il faut que les chapitres ait un titre de la forme <code>titre du livre/titre du chapitre</code> (avec un slash / servant de séparateur).
Par exemple la page <code>Motivation</code> ne veut rien dire hors de son contexte : un autre livre peut également avoir un chapitre nommé '''Motivation'''. Il vaut mieux la nommer <code>Pédagogie : psychologie cognitive appliquée à l'enseignement/motivation</code>.
[[Wikilivres:Conventions sur les titres]]
-- ◄ [[Utilisateur:DavidL|'''D'''avid '''L''']] • [[Discussion Utilisateur:DavidL|discuter]] ► 9 novembre 2013 à 15:13 (CET)
== Share your experience and feedback as a Wikimedian in this global survey ==
<div class="plainlinks mw-content-ltr" lang="fr" dir="ltr">
Bonjour ! La Fondation Wikimédia vous demande votre avis dans une enquête. Nous souhaitons savoir si nous répondons bien à vos attentes dans votre travail sur les wikis et à l’extérieur, et ce que nous pouvons faire pour améliorer les choses à l’avenir.<ref group=survey>Le principal objectif de l’enquête est d’obtenir des retours sur le travail actuel de la Fondation Wikimédia, sans stratégie à long terme.</ref> Les opinions que vous partagez affecteront directement le travail actuel et futur de la Fondation Wikimédia. Vous avez été choisi au hasard pour répondre à l’enquête, car nous souhaitons obtenir des informations de la part de votre communauté Wikimédia. Afin de vous remercier pour le temps accordé, nous livrons 20 t-shirts Wikimédia à des personnes choisies au hasard parmi celles ayant répondu à l’enquête.<ref group=survey>Questions légales : pas d’achat nécessaire. Vous devez avoir l’âge de la majorité pour participer. Soutenu par la Fondation Wikimédia (Wikimedia Foundation) située au 149 New Montgomery, San Francisco, CA, USA, 94105. Se termine le 31 janvier 2017. Le don est annulé en cas d’infraction. [[m:Community Engagement Insights/2016 contest rules|Cliquez ici pour lire les règles du concours]].</ref> Le questionnaire est disponible dans différents langages et requiert entre 20 et 40 minutes pour être complété.
<strong>'''[https://wikimedia.qualtrics.com/SE/?SID=SV_6mTVlPf6O06r3mt&Aud=VAE&Src=57VAEOP Répondez-y maintenant !]'''</strong>
Vous pouvez lire davantage d’informations sur [[m:Community_Engagement_Insights/About_CE_Insights|ce projet]]. Cette enquête est hébergée par un service tiers et régie par sa [[:foundation:Community_Engagement_Insights_2016_Survey_Privacy_Statement|déclaration de confidentialité]]. Visitez notre [[m:Community_Engagement_Insights/Frequently_asked_questions|page des questions fréquentes]] pour trouver plus d’informations sur cette enquête. Si vous avez besoin d’aide supplémentaire, ou désirez vous désabonner des communications futures au sujet de cette enquête, envoyez un courrier électronique à surveys@wikimedia.org.
Merci !
--[[:m:User:EGalvez (WMF)|EGalvez (WMF)]] ([[:m:User talk:EGalvez (WMF)|talk]]) 13 janvier 2017 à 23:25 (CET)
</div>
<!-- Message envoyé par User:EGalvez (WMF)@metawiki en utilisant la liste à https://meta.wikimedia.org/w/index.php?title=Community_Engagement_Insights/MassMessages/Lists/2016/57-VAEOP&oldid=16205400 -->
<references group=survey />
== Mewtow, vous aussi venez discuter de l'avenir du mouvement ==
Bonjour,
Je viens vous inviter à rejoindre les discussions en cours sur le futur du mouvement Wikimédia.
Depuis quelques jours déjà, des contributeurs, à travers divers projets et langues sur wikis, discutent de nos priorités, en tant que mouvement, pour les 15 années à venir : Quelle est la chose la plus importante à faire ? Comment maintenir ou accélérer notre progrès? Qu'est-ce qui pourrait consolider notre unité en tant que mouvement? Voici autant de questions auxquelles tous essaient d’apporter des réponses.
Vous aussi, votre point de vue compte énormement. On prend l'avis de tout le monde y compris les correcteurs d'orthographe {{sourire}}.
N’hésitez surtout pas à consulter la page des discussions sur [[Wikilivres:Mouvement Wikimedia/Stratégie 2017]] pour y laisser vos propositions ou pour débattre des propositions des autres.
Voici quelques liens utiles sur le contexte et le processus de cette consultation :
* [[M:Strategy/Wikimedia movement/2017/Process/Briefing/fr|Contexte général]], pour comprendre les objectifs et jeter un coup d'oeil aux études et statistiques connexes
* [[M:Strategy/Wikimedia movement/2017/Process/fr|Calendrier et processus]], pour plus de détails sur les dates et consulter le graphique illustrant toutes les étapes
* [[M:Strategy/Wikimedia_movement/2017/Frequently_asked_questions/fr|Foire Aux Questions (FAQ)]], pour les questions qui reviennent fréquemment
N'hésitez pas à me contacter au besoin. Amicalement, [[Utilisateur:Samuel (WMF)|Samuel (WMF)]] ([[Discussion utilisateur:Samuel (WMF)|discussion]]) 29 mars 2017 à 17:32 (CEST)
== Discussion des principes de Fonctionnement de Wikibooks ==
Bonjour,
j'ai modifié des pages d'aide : [[Wikilivres/Introduction]], [[Wikilivres/Démarrer un wikilivre]] et créé [[Wikilivres/Pourquoi écrire sur Wikibooks ?]]. Mes modifications concernent les principes de fonctionnement de Wikibooks, sur le travail coopératif, le respect des auteurs et le droit de signer ses livres. Tous les contributeurs sont concernés. Une discussion est initiée sur [[Discussion:Wikilivres/Démarrer un wikilivre]]. J'aimerais bien avoir votre avis.
[[Utilisateur:Thierry Dugnolle|Thierry Dugnolle]] ([[Discussion utilisateur:Thierry Dugnolle|discussion]]) 27 avril 2017 à 16:43 (CEST)
== Notes ==
== Relecture ==
Bonjour,
Afin d'éviter toute « collision » lors d'éditions simultanées, ce serait bien que tu m'indiques directement que tel ou tel chapitre est en état d'être relu !
Amitiés, [[Utilisateur:Jean-Jacques MILAN|Jean-Jacques MILAN]] ([[Discussion utilisateur:Jean-Jacques MILAN|discussion]]) 26 juin 2017 à 10:09 (CEST).
:Bonjour,
:Pour le moment, à l'exception des trois chapitres sur les satellites et les corps transneptuniens, tous les chapitres sont terminés et en état d'être relus/corrigés.
:Cordialement.
== [[Architecture des ordinateurs]] ==
Bonjour Mewtow,
Un utilisateur [[Discussion:Architecture des ordinateurs#Fusion|suggère de fusionner]] l'ébauche de livre [[Architecture des ordinateurs]] dans [[Fonctionnement d'un ordinateur]]. Si des éléments te semblent viables, n'hésite pas à t'en servir. Sinon, je déplacerai [[Architecture des ordinateurs]] vers des brouillons afin de ne pas encombrer la wikilibrairie.
Bienvenue sur Wikibooks, puisque nous ne sommes pas croisés. Bonne continuation ! Bien cordialement, 10 juillet 2017 à 16:03 (CEST)
:Bonjour Savant-fou.
:Après lecture du cours en question, je ne pense pas que je pourrais réutiliser quoique ce soit du cours "Architecture des ordinateurs" (que je connaissais depuis longtemps, mais n'avais pas souhaité modifier). La quasi-totalité de ce qui est présent dans ce cours l'est aussi dans le mien, avec cependant quelques exemples ou exercices en plus que je ne penses pas pertinent d'insérer dans mon ouvrage. Je ne m'opposerais pas du tout à ce que vous le déplaciez vers des brouillons, comme vous l'avez mentionné.
:Cordialement.
:[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 10 juillet 2017 à 19:09 (CEST)
::Merci pour ta réponse. J'ai déplacé Architecture des ordinateurs vers [[Utilisateur:Merrheim/Architecture_des_ordinateurs]]. La page [[Architecture des ordinateurs]] redirige maintenant vers [[Fonctionnement d'un ordinateur]]. Bonne journée ! [[Fichier:P_physics.svg|25px|link=User:Savant-fou]] [[Utilisateur:Savant-fou|Savant-fou]] <small><sup>[[Discussion Utilisateur:Savant-fou|parler]]</sup></small> 11 juillet 2017 à 09:54 (CEST)
== Share your experience and feedback as a Wikimedian in this global survey ==
<div class="mw-parser-output">
<div class="plainlinks mw-content-ltr" lang="fr" dir="ltr">
Bonjour ! La Fondation Wikimédia vous demande votre avis dans une enquête. Nous souhaitons savoir si nous répondons bien à vos attentes dans votre travail sur les wikis et à l’extérieur, et ce que nous pouvons faire pour améliorer les choses à l’avenir. Les opinions que vous partagez affecteront directement le travail actuel et futur de la Fondation Wikimédia. Vous avez été choisi·e au hasard pour répondre à l’enquête, car nous souhaitons obtenir des informations de la part de votre communauté Wikimédia. Le questionnaire est disponible dans différentes langues et requiert entre 20 et 40 minutes pour être complété.
<big>'''[https://wikimedia.qualtrics.com/jfe/form/SV_5ABs6WwrDHzAeLr?aud=VAE&prj=ot&edc=6&prjedc=ot6 Répondez-y maintenant !]'''</big>
Vous pouvez trouver davantage d’informations concernant cette enquête [[m:Special:MyLanguage/Community_Engagement_Insights/About_CE_Insights|sur la page du projet]] et voir comment vos retours aident la Fondation Wikimédia à prendre en charge des contributeurs comme vous. Cette enquête est hébergée par un service tiers et régie par cette [[:foundation:Community_Engagement_Insights_2018_Survey_Privacy_Statement|déclaration de confidentialité]] (en anglais). Visitez notre [[m:Special:MyLanguage/Community_Engagement_Insights/Frequently_asked_questions|page des questions fréquentes]] pour trouver plus d’informations sur cette enquête. Si vous avez besoin d’aide supplémentaire, ou désirez vous désabonner des communications futures au sujet de cette enquête, envoyez un courrier électronique via la fonctionnalité EmailUser à [[:m:Special:EmailUser/WMF Surveys|WMF Surveys]] pour être supprimé·e de la liste.
Merci !
</div> <span class="mw-content-ltr" dir="ltr">[[m:User:WMF Surveys|WMF Surveys]]</span>, 29 mars 2018 à 20:36 (CEST)
</div>
<!-- Message envoyé par User:WMF Surveys@metawiki en utilisant la liste à https://meta.wikimedia.org/w/index.php?title=Community_Engagement_Insights/MassMessages/Lists/2018/ot6&oldid=17881403 -->
== Reminder: Share your feedback in this Wikimedia survey ==
<div class="mw-parser-output">
<div class="plainlinks mw-content-ltr" lang="fr" dir="ltr">
Chaque réponse à cette enquête peut aider la Fondation Wikimédia à améliorer votre expérience sur les projets Wikimédia. Jusqu’à présent, nous n’avons entendu que 29% des contributeurs Wikimédia. Le sondage est disponible dans plusieurs langues et prend entre 20 et 40 minutes pour être complété. '''[https://wikimedia.qualtrics.com/jfe/form/SV_5ABs6WwrDHzAeLr?aud=VAE&prj=ot&edc=6&prjedc=ot6 Répondez-y maintenant.]'''
Si vous avez déjà répondu à ce sondage, nous sommes désolé que que vous receviez ce rappel. Nous avons conçu le sondage afin qu'il soit impossible de savoir qui a répondu, aussi nous devons envoyer les rappels à tout le monde.
<span class="mw-translate-fuzzy">Si vous souhaitez ne pas recevoir le prochain rappel ou un autre sondage, envpyez un courriel en utilisant la fonction d'envoi à [[:m:Special:EmailUser/WMF Surveys|WMF Surveys]]</span>. Vous pouvez également envoyer toute question que vous auriez à cette adresse d’utilisateur. [[m:Community_Engagement_Insights/About_CE_Insights|Lisez-en plus sur cette enquête sur la page du projet.]] Le sondage est hébergé par un service tiers et régi par cette [[:foundation:Community_Engagement_Insights_2018_Survey_Privacy_Statement|déclaration de confidentialité]] de la Fondation Wikimédia. Merci !
</div> <span class="mw-content-ltr" dir="ltr">[[m:User:WMF Surveys|WMF Surveys]]</span>, 13 avril 2018 à 03:35 (CEST)
</div>
<!-- Message envoyé par User:WMF Surveys@metawiki en utilisant la liste à https://meta.wikimedia.org/w/index.php?title=Community_Engagement_Insights/MassMessages/Lists/2018/ot6&oldid=17881403 -->
== Your feedback matters: Final reminder to take the global Wikimedia survey ==
<div class="mw-parser-output">
<div class="plainlinks mw-content-ltr" lang="fr" dir="ltr">
Bonjour ! Voici un dernier message pour vous rappeler que le questionnaire de l’enquête de la Fondation Wikimédia se fermera le '''23 avril 2018 à 07 h (UTC)'''. Le questionnaire est disponible dans différentes langues et demande entre 20 et 40 minutes pour être complété. '''[https://wikimedia.qualtrics.com/jfe/form/SV_5ABs6WwrDHzAeLr?aud=VAE&prj=ot&edc=6&prjedc=ot6 Répondez-y maintenant.]'''
'''Si vous avez déjà participé à ce sondage, merci ! Nous ne vous dérangerons plus.''' Nous avons conçu le sondage afin qu'il soit impossible de savoir qui a répondu, aussi nous devons envoyer les rappels à tout le monde. Pour vous désabonner des sondages futurs, envoyez un courrier électronique via la fonctionnalité EmailUser à [[:m:Special:EmailUser/WMF Surveys|WMF Surveys]]. Vous pouvez également envoyer toute question que vous auriez à cette adresse d’utilisateur. [[m:Community_Engagement_Insights/About_CE_Insights|Lisez-en plus sur cette enquête sur la page du projet.]] Le sondage est hébergé par un service tiers et régi par cette [[:foundation:Community_Engagement_Insights_2018_Survey_Privacy_Statement|déclaration de confidentialité]] de la Fondation Wikimédia.
</div> <span class="mw-content-ltr" dir="ltr">[[m:User:WMF Surveys|WMF Surveys]]</span>, 20 avril 2018 à 02:44 (CEST)
</div>
<!-- Message envoyé par User:WMF Surveys@metawiki en utilisant la liste à https://meta.wikimedia.org/w/index.php?title=Community_Engagement_Insights/MassMessages/Lists/2018/ot6&oldid=17952438 -->
== Community Insights Survey ==
<div class="plainlinks mw-content-ltr" lang="fr" dir="ltr">
'''Faites part de votre expérience dans cette enquête'''
Bonjour {{PAGENAME}},
La Wikimedia Foundation sollicite votre point de vue dans une enquête sur votre expérience avec {{SITENAME}} et Wikimedia. L’objectif de cette enquête est de savoir à quel niveau la ''Foundation'' soutient correctement (ou non) votre travail sur le wiki, et de quelle manière nous pouvons changer ou améliorer les choses à l’avenir. Les avis que vous émettrez impacteront directement le travail actuel et futur de la Wikimedia Foundation.
Merci de prendre 15 à 25 minutes pour '''[https://wikimedia.qualtrics.com/jfe/form/SV_0pSrrkJAKVRXPpj?Target=CI2019List(other,act5) donner votre point de vue à travers cette enquête]'''. Elle est disponible dans plusieurs langues.
Cette enquête est hébergée par un tiers et [https://foundation.wikimedia.org/wiki/Community_Insights_2019_Survey_Privacy_Statement régie par cette déclaration de confidentialité] (en anglais).
Vous pouvez lire [[m:Community Insights/Frequent questions|plus d’informations sur ce projet]]. [mailto:surveys@wikimedia.org Envoyez-nous un courriel] si vous avez une quelconque question ou si vous ne voulez pas recevoir d’autres messages concernant cette enquête.
Cordialement,
</div> [[User:RMaung (WMF)|RMaung (WMF)]] 9 septembre 2019 à 16:34 (CEST)
<!-- Message envoyé par User:RMaung (WMF)@metawiki en utilisant la liste à https://meta.wikimedia.org/w/index.php?title=CI2019List(other,act5)&oldid=19352874 -->
== Reminder: Community Insights Survey ==
<div class="plainlinks mw-content-ltr" lang="fr" dir="ltr">
'''Faites part de votre expérience dans cette enquête'''
Bonjour {{PAGENAME}},
Il y a quelques semaines, nous vous avons invité à participer à l’enquête sur les Perspectives de la communauté. Il s’agit de l’enquête annuelle de la Wikimedia Foundation auprès de nos communautés internationales. Nous voulons savoir à quel niveau nous soutenons correctement (ou non) votre travail sur le wiki. Nous avons atteint 10% de notre objectif de participation. Si vous n’avez pas encore participé à l’enquête, vous pouvez nous aider à atteindre notre objectif ! '''Votre voix compte à nos yeux'''.
Merci de prendre 15 à 25 minutes pour '''[https://wikimedia.qualtrics.com/jfe/form/SV_0pSrrkJAKVRXPpj?Target=CI2019List(other,act5) donner votre point de vue à travers cette enquête]'''. Elle est disponible dans plusieurs langues.
Cette enquête est hébergée par un tiers et [https://foundation.wikimedia.org/wiki/Community_Insights_2019_Survey_Privacy_Statement régie par cette déclaration de confidentialité] (en anglais).
Vous pouvez lire [[m:Community Insights/Frequent questions|plus d’informations sur ce projet]]. [mailto:surveys@wikimedia.org Envoyez-nous un courriel] si vous avez une quelconque question ou si vous ne voulez pas recevoir d’autres messages concernant cette enquête.
Cordialement,
</div> [[User:RMaung (WMF)|RMaung (WMF)]] 20 septembre 2019 à 21:14 (CEST)
<!-- Message envoyé par User:RMaung (WMF)@metawiki en utilisant la liste à https://meta.wikimedia.org/w/index.php?title=CI2019List(other,act5)&oldid=19395141 -->
== Reminder: Community Insights Survey ==
<div class="plainlinks mw-content-ltr" lang="fr" dir="ltr">
'''Faites part de votre expérience dans cette enquête'''
Bonjour {{PAGENAME}},
Il reste quelques semaines seulement pour participer à l’enquête sur les Perspectives de la communauté ! Nous avons atteint 30% de notre objectif de participation. Si vous n’avez pas encore participé à l’enquête, vous pouvez nous aider à atteindre notre objectif !
Avec ce sondage, la Wikimedia Foundation recueille un retour sur le niveau de qualité de notre soutien de votre travail sur le wiki. Seules 15–25 minutes sont nécessaires pour achever de répondre à l’enquête, et cela a un impact direct sur le soutien que nous apportons.
Merci de prendre 15 à 25 minutes pour '''[https://wikimedia.qualtrics.com/jfe/form/SV_0pSrrkJAKVRXPpj?Target=CI2019List(other,act5) donner votre point de vue à travers cette enquête]'''. Elle est disponible dans plusieurs langues.
Cette enquête est hébergée par un tiers et [https://foundation.wikimedia.org/wiki/Community_Insights_2019_Survey_Privacy_Statement régie par cette déclaration de confidentialité] (en anglais).
Vous pouvez lire [[m:Community Insights/Frequent questions|plus d’informations sur ce projet]]. [mailto:surveys@wikimedia.org Envoyez-nous un courriel] si vous avez une quelconque question ou si vous ne voulez pas recevoir d’autres messages concernant cette enquête.
Cordialement,
</div> [[User:RMaung (WMF)|RMaung (WMF)]] 4 octobre 2019 à 19:04 (CEST)
<!-- Message envoyé par User:RMaung (WMF)@metawiki en utilisant la liste à https://meta.wikimedia.org/w/index.php?title=CI2019List(other,act5)&oldid=19435548 -->
== huit gluons au lieu de neuf ==
Bonjour Mewtow,
merci pour votre wikilivre sur le noyau atomique, que je commence juste à lire.
Concernant les gluons, compte tenu de leurs attributs :
1 - une couleur parmi trois,
2 - une anti couleur parmi trois,
ça devrait faire 3x3 '''neuf''' gluons.
Il n'y en a bien que huit mais uniquement pour une raison théorique difficile à piger pour moi,
cf [[w:Gluon#Pourquoi huit gluons au lieu de neuf ?]]
Çà me semble important de le souligner dans le wikilivre, car le premier pignouf (comme moi)
qui fera le calcul trouvera ... 9 et pensera que l'auteur du wikilivre s'est trompé.
cordialement
--[[Utilisateur:JihemD|JihemD]] ([[Discussion utilisateur:JihemD|discussion]]) 13 août 2020 à 17:20 (CEST)
: Effectivement, il y a bien une erreur. Je corrige de suite. Merci d'avoir fait la remarque.[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 13 août 2020 à 21:48 (CEST)
: Et j'en ai profité pour donner l'explication du pourquoi huit gluons et non neuf. A voir si l'explication que je donnes est facile à comprendre ou pas. A mon avis, la superposition des gluons devrait être difficile à comprendre, mais le reste de l'explication devrait passer.[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 13 août 2020 à 22:32 (CEST)
== Appel à relecture et création d'hyperliens ==
Bonjour Mewtow.
Je découvre avec plaisir le projet Wikilivres en décidant de publier les trois premiers chapitres de ma thèse de doctorat sous forme de livre. En parcourant le contenu, je suis tombé sur les nombreux ouvrage que tu as déjà produit ici. De là m'est venu une idée. Plutôt que de mettre dans mon texte des wikiliens pointant vers Wikipédia pour aider les lecteurs à trouver un complément d'information, pourquoi pas les rediriger vers des travaux déjà existants sur Wikilivres. Le problème, c'est que je n'ai pas vraiment le temps de parcourir tout ceux-ci.
C'est donc la raison pour laquelle je viens poster ce message pour voir si tu ne pourrais pas m'aider dans cette tâche. Toi qui connais bien le contenu de tes livres, tu pourrais effectivement créer toi-même dans livre que je suis en train de terminer les hyperliens qui pourraient pointer vers tes travaux. L'idéal serait même de remplacer tous ceux qui pointent vers Wikipédia pour éviter que le lecteur ne doive surfer sur deux sites différents lors d'une consultation hors ligne notamment.
Tu me diras ce que tu en penses. Voici déjà le titre de mon ouvrage : [[Le mouvement Wikimédia]]. N'hésite pas non plus à faire des remarques sur sa page de discussion si jamais lors d'une éventuelle lecture, tu trouverais des informations fausses. Dès que j'en aurai le temps, je passerai aussi du temps sur tes ouvrages qui ont l'air intéressants.
Bien à toi, [[User:Lionel Scheepmans|Lionel Scheepmans]] <sup><big>✉</big> [[User talk:Lionel Scheepmans|Contact]]</sup> <sub>Désolé pour ma [[w:dysorthographie|dysorthographie]], [[w:dyslexie|dyslexie]] et [[wikt:distraction|"dys"traction]].</sub> 29 novembre 2021 à 14:48 (CET)
== Prise de décision - fin dimanche 2 juin ==
Bonsoir Mewtow,
Tout d'abord un grand merci pour ta contribution très active à Wikilivres.
Je t'informe qu'une "prise de décision" (débat) est en cours concernant le retrait automatique des liens rouges lorsqu'une image est supprimée sur Commons. Lien : [[Wikilivres:Prise de décision/Laisser les liens rouges des images]].
'''La phase de discussion se termine le dimanche 2 juin à 23 h 59.'''
D'avance, merci pour ta participation !
Cdlt, [[Utilisateur:Fourmidable|Fourmidable]] ([[Discussion utilisateur:Fourmidable|discussion]]) 25 mai 2024 à 23:56 (CEST)
== Ouvrages sources des wikibooks sur l'Electricité et l'Electronique ==
Bonjour Mewtow,
Je suis en train de lire les wikibooks sur l'Electricité et l'Electronique dont tu es l'auteur, et je te remercie beaucoup pour l'excellent travail que tu as fait!! :)
Quelles sont les ouvrages sources dont tu t'es servi pour les écrire ?
En te remerciant par avance.
Cdlt,
[[Utilisateur:Alex Mtlr|Alex Mtlr]] ([[Discussion utilisateur:Alex Mtlr|discussion]]) 16 juillet 2025 à 13:57 (CEST)
:Bonjour Alex.
:Pour être franc, les sources principales sont les cours que j'ai eu pendant ma scolarité, lors de mon Bac STI option génie électronique, et mon BTS systèmes électroniques. Pour ce qui est du cours d'électronique, notamment tout ce qui a trait aux transistors, je me souviens avoir utilisé un bouquin nommé "Circuits microélectroniques", par Adel Sedra et Kenneth C. Smith, que j'ai revendu il y a de cela quelques années. J'ai aussi ai lu pas mal de fichiers .pdf disponibles sur le net, accessibles via quelques recherches Google, mais ca fait un moment que j'ai rédigé ces cours et j'ai un peu oublié lesquels.
:C'est tout ce dont je me souviens pour le moment.
:Cordialement.
:[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 16 juillet 2025 à 16:24 (CEST)
== Pages orphelines ==
Bonjour, Il semble que certaines de vos pages se sont perdues dans les pages orphelines. Pourriez-vous les garder ici dans votre page de discussion pour qu'elles ne soient plus orphelines. Merci [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 25 février 2026 à 14:07 (CET)
:Pas de problème pour les garder sur ma page de discussion. Par contre, certaines des pages en question sont à supprimer, car leur contenu a été déplacé dans des chapitres existants. Faudra que je regarde cela cette semaine et que je me renseigne sur la procédure pour les faire supprimer.
:Dans la liste précédente, les deux chapitres suivants sont dans ce cas :
:* [[Les opérations bit à bit/Calculs flottants]]
:* [[Les suites et séries/Les suites arithmético-géométriques]]
:Il y a aussi ceux identifiés dans "Fontionnement d'un ordinateur", qui ont été identifiés ailleurs. Je n'ai aucun chapitre brouillon pour ce cours. [[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 25 février 2026 à 20:04 (CET)
::Bonjour,
::* Voir le Bistro, il semble que cela soit difficile de supprimer une page
::* Il y a une vingtaine de livres dans ta situation. Je les ai regroupés dans ce livre. Voir les derniers chapitres, la présentation des premiers chapitres laisse à désirer
::*.
::* [[feuilles dupliquées orphelines|feuilles dupliquées orphelines]]
::*.
::Par exemple pour ton cas :
::*.
::*.
::La page orpheline :
::* [[Les opérations bit à bit/Calculs flottants|Les opérations bit à bit/Calculs flottants]]
::a été copiée ici:
::* [[Les opérations bit à bit/Les opérations arithmétiques sur des flottants|Les opérations bit à bit/Les opérations arithmétiques sur des flottants]]
::*.
::La page orpheline :
::* [[Les suites et séries/Les suites arithmético-géométriques|Les suites et séries/Les suites arithmético-géométriques]]
::a été copiée ici:
::* [[Les suites et séries/Les suites numériques|Les suites et séries/Les suites numériques]]
::*.
::Tu peux naturellement simplement laisser tes pages sur ta page en attendant qu'elles puissent être supprimées
::Où je peux les introduire pour toi dans le livre [[feuilles dupliquées orphelines|feuilles dupliquées orphelines]] en attendant qu'une bonne âme se sacrifie pour supprimer les doublons. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 26 février 2026 à 00:02 (CET)
:::Pas de problèmes de mon côté pour que tu ajoutes ces pages dans le livre ' feuilles dupliquées orphelines" à ma place, merci beaucoup. Je crois que ce serait la meilleure solution, vu que les chapitres dupliqués orphelins de "Fonctionnement d'un ordinateur" sont déjà dessus [[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 26 février 2026 à 01:34 (CET)
::::Merci pour ton aide. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 26 février 2026 à 11:15 (CET)
* [[Le noyau atomique/Les diffusions nucléaires]]
* [[Électricité/L'étude des signaux quelconques]]
* [[Le noyau atomique/Les diffusions nucléaires]]
* [[Les opérations bit à bit/Calculs flottants]]
* [[Les suites et séries/Les suites arithmético-géométriques]]
* [[Mémoire/Habiletés]]
* [[Mémoire/Mémoire perceptive]]
* [[Mémoire/Mémoire à court terme]]
* [[Mémoire/Mémoires sensorielles]]
* [[Mémoire/Oubli]]
== Demandes de prises de décision ==
Bonjour, Pour info.
J'ai posé deux demandes de prises de décisions concernant le sujet suivant.
* '''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Cr%C3%A9er_un_espace_lecteurs_et_un_espace_auteurs_sur_la_page_d%27accueil Créer un espace lecteurs et un espace Les coulisses de Wikibook]'''
* '''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]'''
Merci [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 16:53 (CEST)
49m78br1cnaaceq4h6ppuv1e77jdj4u
Discussion utilisateur:Xhungab
3
55548
772076
771393
2026-09-12T10:02:44Z
Xhungab
23827
772076
wikitext
text/x-wiki
<templatestyles src="Main Page/minerva.css" />
<div style="display: none;">
Version : {{CURRENTTIMESTAMP}}
({{CURRENTHOUR}}h.. UTC)
<!-- Page à purger tous les jours pour tous. -->
Cette page a besoin de __NOCACHE__
<div style="text-align:center; font-size:95%; margin:0;">[[Wikilivres:Présentation|Wikilivres ?]] • [[Wikilivres|Aide]] • [[Wikilivres:Accueil|Communauté]]</div>
<div id="pagecount" style="font-size:85%;">[[:Catégorie:Livres par titre|{{NUMBEROFBOOKS}} livres]] contenant [[Special:Allpages|{{NUMBEROFARTICLES}} pages]].</div>
</div>
'''Dans cette page, vous trouverez la liste de tous les livres disponibles sur Wikibook. Vous trouverez en particulier des livres en construction et vous pourrez entrer en contact avec les auteurs. Vous trouverez aussi des livres en construction sans auteur actif que vous pourrez compléter.'''
{| class="flexible"
| style="width:25%;" |
[[Fichier:Book icon.png|100px|link=Wikilivres:Tous les livres]]
| style="width:25%;" |
* [[:Catégorie:Classe 7 - Arts. Divertissements. Sports|Arts]]
* [[:Catégorie:Loisirs|Loisirs]]
* [[:Catégorie:Histoire|Histoire]]
| style="width:25%;" |
* [[:Catégorie:Classe 8 - Langue. Linguistique. Littérature|Langues]]
* [[:Catégorie:Technologie|Technologie]]
* [[:Catégorie:Informatique|Informatique]]
| style="width:25%;" |
* [[:Catégorie:Classe 5 - Mathématique. Sciences exactes et naturelles|Sciences]]
* [[:Catégorie:Sciences humaines|Sciences humaines]]
* '''[[Wikilivres:Tous les livres|Tous les livres]]'''
|}
<div style="width:100%; margin:0px; padding:0px 5px 5px 5px;">
<div id="mf-index" title="Index des livres" class="only_mobile">
* [[:Catégorie:Classe 7 - Arts. Divertissements. Sports|Arts]]
* [[:Catégorie:Informatique|Informatique]]
* [[:Catégorie:Classe 8 - Langue. Linguistique. Littérature|Langues]]
* [[:Catégorie:Loisirs|Loisirs]]
* [[:Catégorie:Classe 5 - Mathématique. Sciences exactes et naturelles|Sciences]]
* [[:Catégorie:Sciences humaines|Sciences humaines]]
* [[:Catégorie:Technologie|Technologie]]
* '''[[Wikilivres:Tous les livres|Tous les livres]]'''
</div>
__NOTOC__
__NOEDITSECTION__
[[Catégorie:Wikilivres]]
luakj40hfp9h7bduylw9ln9h26byufb
Utilisateur:Xhungab
2
56767
772039
771891
2026-09-11T21:25:08Z
Xhungab
23827
772039
wikitext
text/x-wiki
<templatestyles src="Main Page/minerva.css" />
<div style="display: none;">
Version : {{CURRENTTIMESTAMP}}
({{CURRENTHOUR}}h.. UTC)
<!-- Page à purger tous les jours pour tous. -->
Cette page a besoin de __NOCACHE__
</div>
{| class="flexible" style="width:100%; margin:0px; padding:0px 5px 5px 5px;"
|
<div style="width:auto; text-align:center;">
<div id="mf-banner1" style="font-size:162%; padding:0.1em;">Bienvenue sur [[Wikilivres:Présentation|Wikilivres]],</div>
<div id="mf-banner2" style="font-size:95%; margin-top:0.2em;">{{Bloc|La bibliothèque}} {{Bloc|de livres pédagogiques libres}} {{Bloc|que chacun peut améliorer.}}</div>
<div style="text-align:center; font-size:95%; margin:0;">[[Wikilivres:Présentation|Wikilivres ?]] • [[Wikilivres|Aide]] • [[Wikilivres:Accueil|Communauté]]</div>
<div id="pagecount" style="font-size:85%;">[[:Catégorie:Livres par titre|{{NUMBEROFBOOKS}} livres]] contenant [[Special:Allpages|{{NUMBEROFARTICLES}} pages]].</div>
</div>
| style="width:55%; font-size:95%;" |
{| class="flexible"
| style="width:25%;" |
[[Fichier:Book icon.png|100px|]]
| style="width:50%;" |
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
| style="width:10%;" |
| style="width:0%;" |
|}
|}
'''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Cr%C3%A9er_un_espace_lecteurs_et_un_espace_auteurs_sur_la_page_d%27accueil Pour soutenir le nouveau Wikibook 2026, Le Wikibook De Vos Rêve c'est Ici.]'''
<div class="livre-accueil-section2" style="clear:both; margin:33px 0px 33px 0px; padding:0px;">
{| class="livre-accueil-table2" style="width:100%; border-spacing:8px 8px;"
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre1" title="Exemple de livre en vitrine">{{:Accueil/Vitrine|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre2" title="Exemple de livre Wikijunior">{{:Accueil/Wikijunior|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre3" title="Exemple de recette de cuisine">{{:Accueil/Recette|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
|}
</div>
<div id="mf-newbooks" class="newbooks-{{PAGESINCATEGORY:Nouveaux livres}}" title="Nouveaux livres" style="text-align: center">
'''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''
'''[[:Catégorie:Nouveaux livres|..]] Nouveautés et Projets En Construction Actif:''' <dynamicpagelist>
category=Nouveaux livres
namespace=Main
suppresserrors=true
order=descending
count=20
mode=inline
</dynamicpagelist>
</div>
<hr style="border: 1px solid #4faff380; margin: 10px; border-top: 0px;" />
</div>
<div class="projects only_nonmobile">{{Projets Wikimedia}}</div>
<div class="projects only_mobile">{{Projets Wikimedia|Mobile}}</div>
<div style="margin-top:12px;border:1px solid #4faff380;border-radius:10px;text-align:center;" class="plainlinks">
[[Wikilivres:Avertissements généraux|Wikilivres ne garantit pas le contenu mis en ligne]].
<div style="font-size:85%;">La [{{fullurl:wikimedia:Accueil|uselang=fr}} Wikimedia Foundation] étant un hébergeur, elle ne saurait être tenue responsable des erreurs éventuelles contenues sur ce site.<br />Chaque rédacteur est responsable de ses contributions.</div>
</div>
__NOTOC__
__NOEDITSECTION__
[[Catégorie:Wikilivres]]
8z77ttyj5faxxdzf7swk5v755c1zpj8
772040
772039
2026-09-11T21:28:49Z
Xhungab
23827
772040
wikitext
text/x-wiki
<templatestyles src="Main Page/minerva.css" />
<div style="display: none;">
Version : {{CURRENTTIMESTAMP}}
({{CURRENTHOUR}}h.. UTC)
<!-- Page à purger tous les jours pour tous. -->
Cette page a besoin de __NOCACHE__
</div>
{| class="flexible" style="width:100%; margin:0px; padding:0px 5px 5px 5px;"
|
<div style="width:auto; text-align:center;">
<div id="mf-banner1" style="font-size:162%; padding:0.1em;">Bienvenue sur [[Wikilivres:Présentation|Wikilivres]],</div>
<div id="mf-banner2" style="font-size:95%; margin-top:0.2em;">{{Bloc|La bibliothèque}} {{Bloc|de livres pédagogiques libres}} {{Bloc|que chacun peut améliorer.}}</div>
<div style="text-align:center; font-size:95%; margin:0;">[[Wikilivres:Présentation|Wikilivres ?]] • [[Wikilivres|Aide]] • [[Wikilivres:Accueil|Communauté]]</div>
<div id="pagecount" style="font-size:85%;">[[:Catégorie:Livres par titre|{{NUMBEROFBOOKS}} livres]] contenant [[Special:Allpages|{{NUMBEROFARTICLES}} pages]].</div>
</div>
| style="width:55%; font-size:95%;" |
{| class="flexible"
| style="width:25%;" |
[[Fichier:Book icon.png|100px|]]
| style="width:50%;" |
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
| style="width:10%;" |
| style="width:0%;" |
|}
|}
'''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Cr%C3%A9er_un_espace_lecteurs_et_un_espace_auteurs_sur_la_page_d%27accueil Pour soutenir le nouveau Wikibook 2026, Le Wikibook De Vos Rêve c'est Ici.]'''
<div class="livre-accueil-section2" style="clear:both; margin:33px 0px 33px 0px; padding:0px;">
{| class="livre-accueil-table2" style="width:100%; border-spacing:8px 8px;"
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre1" title="Exemple de livre en vitrine">{{:Accueil/Vitrine|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre2" title="Exemple de livre Wikijunior">{{:Accueil/Wikijunior|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre3" title="Exemple de recette de cuisine">{{:Accueil/Recette|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
|}
</div>
<div id="mf-newbooks" class="newbooks-{{PAGESINCATEGORY:Nouveaux livres}}" title="Nouveaux livres" style="text-align: center">
'''[[:Catégorie:Nouveaux livres|..]] Nouveautés et Projets En Construction Actif:''' <dynamicpagelist>
category=Nouveaux livres
namespace=Main
suppresserrors=true
order=descending
count=20
mode=inline
</dynamicpagelist>
</div>
'''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Wikilivres en cours de rédaction]'''
<hr style="border: 1px solid #4faff380; margin: 10px; border-top: 0px;" />
</div>
<div class="projects only_nonmobile">{{Projets Wikimedia}}</div>
<div class="projects only_mobile">{{Projets Wikimedia|Mobile}}</div>
<div style="margin-top:12px;border:1px solid #4faff380;border-radius:10px;text-align:center;" class="plainlinks">
[[Wikilivres:Avertissements généraux|Wikilivres ne garantit pas le contenu mis en ligne]].
<div style="font-size:85%;">La [{{fullurl:wikimedia:Accueil|uselang=fr}} Wikimedia Foundation] étant un hébergeur, elle ne saurait être tenue responsable des erreurs éventuelles contenues sur ce site.<br />Chaque rédacteur est responsable de ses contributions.</div>
</div>
__NOTOC__
__NOEDITSECTION__
[[Catégorie:Wikilivres]]
5hytitlmoilo8mcftu6nviupqkuoq5t
772043
772040
2026-09-11T21:37:30Z
Xhungab
23827
772043
wikitext
text/x-wiki
<templatestyles src="Main Page/minerva.css" />
<div style="display: none;">
Version : {{CURRENTTIMESTAMP}}
({{CURRENTHOUR}}h.. UTC)
<!-- Page à purger tous les jours pour tous. -->
Cette page a besoin de __NOCACHE__
</div>
{| class="flexible" style="width:100%; margin:0px; padding:0px 5px 5px 5px;"
|
<div style="width:auto; text-align:center;">
<div id="mf-banner1" style="font-size:162%; padding:0.1em;">Bienvenue sur [[Wikilivres:Présentation|Wikilivres]],</div>
<div id="mf-banner2" style="font-size:95%; margin-top:0.2em;">{{Bloc|La bibliothèque}} {{Bloc|de livres pédagogiques libres}} {{Bloc|que chacun peut améliorer.}}</div>
<div style="text-align:center; font-size:95%; margin:0;">[[Wikilivres:Présentation|Wikilivres ?]] • [[Wikilivres|Aide]] • [[Wikilivres:Accueil|Communauté]]</div>
<div id="pagecount" style="font-size:85%;">[[:Catégorie:Livres par titre|{{NUMBEROFBOOKS}} livres]] contenant [[Special:Allpages|{{NUMBEROFARTICLES}} pages]].</div>
</div>
| style="width:55%; font-size:95%;" |
{| class="flexible"
| style="width:25%;" |
[[Fichier:Book icon.png|100px|]]
| style="width:50%;" |
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
| style="width:10%;" |
| style="width:0%;" |
|}
|}
'''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Cr%C3%A9er_un_espace_lecteurs_et_un_espace_auteurs_sur_la_page_d%27accueil Pour soutenir le nouveau Wikibook 2026, Le Wikibook De Vos Rêve c'est Ici.]'''
<div class="livre-accueil-section2" style="clear:both; margin:33px 0px 33px 0px; padding:0px;">
{| class="livre-accueil-table2" style="width:100%; border-spacing:8px 8px;"
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre1" title="Exemple de livre en vitrine">{{:Accueil/Vitrine|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre2" title="Exemple de livre Wikijunior">{{:Accueil/Wikijunior|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre3" title="Exemple de recette de cuisine">{{:Accueil/Recette|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
|}
</div>
<div id="mf-newbooks" class="newbooks-{{PAGESINCATEGORY:Nouveaux livres}}" title="Nouveaux livres" style="text-align: center">
'''[[:Catégorie:Nouveaux livres|..]] Nouveautés et Projets En Construction Actif:''' <dynamicpagelist>
category=Nouveaux livres
namespace=Main
suppresserrors=true
order=descending
count=20
mode=inline
</dynamicpagelist>
</div>
'''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''
<hr style="border: 1px solid #4faff380; margin: 10px; border-top: 0px;" />
</div>
<div class="projects only_nonmobile">{{Projets Wikimedia}}</div>
<div class="projects only_mobile">{{Projets Wikimedia|Mobile}}</div>
<div style="margin-top:12px;border:1px solid #4faff380;border-radius:10px;text-align:center;" class="plainlinks">
[[Wikilivres:Avertissements généraux|Wikilivres ne garantit pas le contenu mis en ligne]].
<div style="font-size:85%;">La [{{fullurl:wikimedia:Accueil|uselang=fr}} Wikimedia Foundation] étant un hébergeur, elle ne saurait être tenue responsable des erreurs éventuelles contenues sur ce site.<br />Chaque rédacteur est responsable de ses contributions.</div>
</div>
__NOTOC__
__NOEDITSECTION__
[[Catégorie:Wikilivres]]
e1imx06omwdix9y4iyrrg2chn15vm0h
772045
772043
2026-09-11T21:43:36Z
Xhungab
23827
772045
wikitext
text/x-wiki
<templatestyles src="Main Page/minerva.css" />
<div style="display: none;">
Version : {{CURRENTTIMESTAMP}}
({{CURRENTHOUR}}h.. UTC)
<!-- Page à purger tous les jours pour tous. -->
Cette page a besoin de __NOCACHE__
</div>
{| class="flexible" style="width:100%; margin:0px; padding:0px 5px 5px 5px;"
|
<div style="width:auto; text-align:center;">
<div id="mf-banner1" style="font-size:162%; padding:0.1em;">Bienvenue sur [[Wikilivres:Présentation|Wikilivres]],</div>
<div id="mf-banner2" style="font-size:95%; margin-top:0.2em;">{{Bloc|La bibliothèque}} {{Bloc|de livres pédagogiques libres}} {{Bloc|que chacun peut améliorer.}}</div>
<div style="text-align:center; font-size:95%; margin:0;">[[Wikilivres:Présentation|Wikilivres ?]] • [[Wikilivres|Aide]] • [[Wikilivres:Accueil|Communauté]]</div>
<div id="pagecount" style="font-size:85%;">[[:Catégorie:Livres par titre|{{NUMBEROFBOOKS}} livres]] contenant [[Special:Allpages|{{NUMBEROFARTICLES}} pages]].</div>
</div>
| style="width:55%; font-size:95%;" |
{| class="flexible"
| style="width:25%;" |
[[Fichier:Book icon.png|100px|]]
| style="width:50%;" |
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
| style="width:10%;" |
| style="width:0%;" |
|}
|}
'''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Cr%C3%A9er_un_espace_lecteurs_et_un_espace_auteurs_sur_la_page_d%27accueil Pour soutenir le nouveau Wikibook 2026, Le Wikibook De Vos Rêve c'est Ici.]'''
<div class="livre-accueil-section2" style="clear:both; margin:33px 0px 33px 0px; padding:0px;">
{| class="livre-accueil-table2" style="width:100%; border-spacing:8px 8px;"
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre1" title="Exemple de livre en vitrine">{{:Accueil/Vitrine|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre2" title="Exemple de livre Wikijunior">{{:Accueil/Wikijunior|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre3" title="Exemple de recette de cuisine">{{:Accueil/Recette|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
|}
</div>
<div id="mf-newbooks" class="newbooks-{{PAGESINCATEGORY:Nouveaux livres}}" title="Nouveaux livres" style="text-align: center">
'''[[:Catégorie:Nouveaux livres|..]] Nouveautés et Projets En Construction Actif:''' <dynamicpagelist>
category=Nouveaux livres
namespace=Main
suppresserrors=true
order=descending
count=20
mode=inline
</dynamicpagelist>
</div>
'''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''
<hr style="border: 1px solid #4faff380; margin: 10px; border-top: 0px;" />
</div>
<div class="projects only_nonmobile">{{Projets Wikimedia}}</div>
<div class="projects only_mobile">{{Projets Wikimedia|Mobile}}</div>
<div style="margin-top:12px;border:1px solid #4faff380;border-radius:10px;text-align:center;" class="plainlinks">
[[Wikilivres:Avertissements généraux|Wikilivres ne garantit pas le contenu mis en ligne]].
<div style="font-size:85%;">La [{{fullurl:wikimedia:Accueil|uselang=fr}} Wikimedia Foundation] étant un hébergeur, elle ne saurait être tenue responsable des erreurs éventuelles contenues sur ce site.<br />Chaque rédacteur est responsable de ses contributions.</div>
</div>
__NOTOC__
__NOEDITSECTION__
[[Catégorie:Wikilivres]]
8v7dwus0mjud1b1a2h9b3nxdocbgk7r
772064
772045
2026-09-12T07:43:41Z
Xhungab
23827
772064
wikitext
text/x-wiki
<templatestyles src="Main Page/minerva.css" />
<div style="display: none;">
Version : {{CURRENTTIMESTAMP}}
({{CURRENTHOUR}}h.. UTC)
<!-- Page à purger tous les jours pour tous. -->
Cette page a besoin de __NOCACHE__
</div>
{| class="flexible" style="width:100%; margin:0px; padding:0px 5px 5px 5px;"
|
<div style="width:auto; text-align:center;">
<div id="mf-banner1" style="font-size:162%; padding:0.1em;">Bienvenue sur [[Wikilivres:Présentation|Wikilivres]],</div>
<div id="mf-banner2" style="font-size:95%; margin-top:0.2em;">{{Bloc|La bibliothèque}} {{Bloc|de livres pédagogiques libres}} {{Bloc|que chacun peut améliorer.}}</div>
<div style="text-align:center; font-size:95%; margin:0;">[[Wikilivres:Présentation|Wikilivres ?]] • [[Wikilivres|Aide]] • [[Wikilivres:Accueil|Communauté]]</div>
<div id="pagecount" style="font-size:85%;">[[:Catégorie:Livres par titre|{{NUMBEROFBOOKS}} livres]] contenant [[Special:Allpages|{{NUMBEROFARTICLES}} pages]].</div>
</div>
| style="width:55%; font-size:95%;" |
{| class="flexible"
| style="width:25%;" |
[[Fichier:Book icon.png|100px|]]
| style="width:50%;" |
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
| style="width:10%;" |
| style="width:0%;" |
|}
|}
<div class="livre-accueil-section2" style="clear:both; margin:33px 0px 33px 0px; padding:0px;">
{| class="livre-accueil-table2" style="width:100%; border-spacing:8px 8px;"
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre1" title="Exemple de livre en vitrine">{{:Accueil/Vitrine|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre2" title="Exemple de livre Wikijunior">{{:Accueil/Wikijunior|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre3" title="Exemple de recette de cuisine">{{:Accueil/Recette|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
|}
</div>
<div id="mf-newbooks" class="newbooks-{{PAGESINCATEGORY:Nouveaux livres}}" title="Nouveaux livres" style="text-align: center">
'''[[:Catégorie:Nouveaux livres|..]] Nouveautés et Projets En Construction Actif:''' <dynamicpagelist>
category=Nouveaux livres
namespace=Main
suppresserrors=true
order=descending
count=20
mode=inline
</dynamicpagelist>
</div>
'''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''
<hr style="border: 1px solid #4faff380; margin: 10px; border-top: 0px;" />
</div>
<div class="projects only_nonmobile">{{Projets Wikimedia}}</div>
<div class="projects only_mobile">{{Projets Wikimedia|Mobile}}</div>
<div style="margin-top:12px;border:1px solid #4faff380;border-radius:10px;text-align:center;" class="plainlinks">
[[Wikilivres:Avertissements généraux|Wikilivres ne garantit pas le contenu mis en ligne]].
<div style="font-size:85%;">La [{{fullurl:wikimedia:Accueil|uselang=fr}} Wikimedia Foundation] étant un hébergeur, elle ne saurait être tenue responsable des erreurs éventuelles contenues sur ce site.<br />Chaque rédacteur est responsable de ses contributions.</div>
</div>
__NOTOC__
__NOEDITSECTION__
[[Catégorie:Wikilivres]]
2wp3oi4fpmatpz401535wmvapi31sws
772074
772064
2026-09-12T09:57:29Z
Xhungab
23827
772074
wikitext
text/x-wiki
<templatestyles src="Main Page/minerva.css" />
<div style="display: none;">
Version : {{CURRENTTIMESTAMP}}
({{CURRENTHOUR}}h.. UTC)
<!-- Page à purger tous les jours pour tous. -->
Cette page a besoin de __NOCACHE__
</div>
{| class="flexible" style="width:100%; margin:0px; padding:0px 5px 5px 5px;"
|
<div style="width:auto; text-align:center;">
<div id="mf-banner1" style="font-size:162%; padding:0.1em;">Bienvenue sur [[Wikilivres:Présentation|Wikilivres]],</div>
<div id="mf-banner2" style="font-size:95%; margin-top:0.2em;">{{Bloc|La bibliothèque}} {{Bloc|de livres pédagogiques libres}} {{Bloc|que chacun peut améliorer.}}</div>
<div style="text-align:center; font-size:95%; margin:0;">[[Wikilivres:Présentation|Wikilivres ?]] • [[Wikilivres|Aide]] • [[Wikilivres:Accueil|Communauté]]</div>
<div id="pagecount" style="font-size:85%;">[[:Catégorie:Livres par titre|{{NUMBEROFBOOKS}} livres]] contenant [[Special:Allpages|{{NUMBEROFARTICLES}} pages]].</div>
</div>
| style="width:55%; font-size:95%;" |
{| class="flexible"
| style="width:25%;" |
[[Fichier:Book icon.png|100px|link=Wikilivres:Tous les livres]]
| style="width:50%;" |
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
| style="width:10%;" |
| style="width:0%;" |
|}
|}
<div class="livre-accueil-section2" style="clear:both; margin:33px 0px 33px 0px; padding:0px;">
{| class="livre-accueil-table2" style="width:100%; border-spacing:8px 8px;"
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre1" title="Exemple de livre en vitrine">{{:Accueil/Vitrine|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre2" title="Exemple de livre Wikijunior">{{:Accueil/Wikijunior|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
| class="livre-presentation livre-accueil2" style="width:33%; padding:10px; vertical-align:top;" |
<div id="mf-livre3" title="Exemple de recette de cuisine">{{:Accueil/Recette|date={{LOCALYEAR}}-{{LOCALMONTH}}-{{LOCALDAY}}}}</div>
|}
</div>
<div id="mf-newbooks" class="newbooks-{{PAGESINCATEGORY:Nouveaux livres}}" title="Nouveaux livres" style="text-align: center">
'''[[:Catégorie:Nouveaux livres|..]] Nouveautés et Projets En Construction Actif:''' <dynamicpagelist>
category=Nouveaux livres
namespace=Main
suppresserrors=true
order=descending
count=20
mode=inline
</dynamicpagelist>
</div>
'''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''
<hr style="border: 1px solid #4faff380; margin: 10px; border-top: 0px;" />
</div>
<div class="projects only_nonmobile">{{Projets Wikimedia}}</div>
<div class="projects only_mobile">{{Projets Wikimedia|Mobile}}</div>
<div style="margin-top:12px;border:1px solid #4faff380;border-radius:10px;text-align:center;" class="plainlinks">
[[Wikilivres:Avertissements généraux|Wikilivres ne garantit pas le contenu mis en ligne]].
<div style="font-size:85%;">La [{{fullurl:wikimedia:Accueil|uselang=fr}} Wikimedia Foundation] étant un hébergeur, elle ne saurait être tenue responsable des erreurs éventuelles contenues sur ce site.<br />Chaque rédacteur est responsable de ses contributions.</div>
</div>
__NOTOC__
__NOEDITSECTION__
[[Catégorie:Wikilivres]]
73yohyewyfwupctddv2a2pkremiljzd
Fonctionnement d'un ordinateur/Les pipelines multicycles
0
65886
771981
765270
2026-09-11T14:16:03Z
Mewtow
31375
/* Les point de contrôle de registre */
771981
wikitext
text/x-wiki
Dans le chapitre précédent, nous avons des exemples de pipelines très simples, comme le pipeline Fetch-Decode-Exec ou le pipeline RISC classique à 5 étages. Sur de tels pipelines, toutes les instructions s'exécutent en un cycle d'horloge, pas un de plus, pas un de moins. Et ce, que ce soit les accès mémoire, les calculs arithmétiques dans l'ALU, les calculs dans l'unité de branchements, ou toute autre opération. Nous appellerons ces pipelines les '''pipelines 1-cycle'''. L'implémentation d'un pipeline 1-cycle est très simple : il suffit d'ajouter des registres au bon endroit, de gérer la propagation des signaux de commande, et de faire quelques autres modifications mineures.
Mais dans les faits, les processeurs de ce type sont très rares. La quasi-totalité des processeurs modernes comme anciens supportent des ''instructions multi-cycle'', à savoir des instructions qui mettent plusieurs cycles à s'exécuter. Un exemple typique est celui des opérations flottantes ou des multiplications. Il y a des instructions multi-cycles dont le nombre de cycles varie suivant l'instruction : les instructions d'accès mémoire sont dans ce cas. Et gérer de telles instructions avec un pipeline pose de nombreux problèmes d'implémentation. Les instructions multi-cycles ne semblent pas vraiment coller avec un pipeline, qui a une longueur fixe. On s'attend à ce qu'il ait un nombre fixe d'étages, qui font chacun un cycle d’horloge.
: Les pipelines multicycles sont souvent appelés des pipelines dynamiques, nous utiliserons les deux termes dans ce cours.
Dans ce chapitre, nous allons voir comment modifier un processeur à pipeline, pour qu'il gère des instructions multi-cycles. La modification donne un '''pipeline multi-cycle''', terme qui indique que le pipeline est modifier pour supporter des instructions multi-cycles. Avec un pipeline multicycle, certaines instructions peuvent prendre 1 cycle, d'autres 7 cycles, d'autres 9, d'autres 25, etc. Les instructions rapides prennent moins de cycles que les autres. L'implémentation d'un pipeline multicycle est assez simple : il y a juste besoin d'utiliser des unités de calcul dédiées pour les instructions lentes, avec par exemple un circuit multiplieur séparé d'une ALU entière. Le temps de calcul dépend de l'ALU, et donc le nombre de cycles de l'instruction associée.
[[File:Pipeline avec un nomrbe variable d'étages par instructions.png|centre|vignette|upright=3|Pipeline avec un nombre variable d'étages par instructions.]]
==Les pipelines multi-cycles séparent ''front-end'' et ''back-end''==
Avec un pipeline multicycle, le processeur est décomposé en deux parties. La première partie, l’'''amont''' (''front end''), prend en charge les étages communs à toutes les instructions. Il correspond au séquenceur et à l'unité de chargement et regroupe la mise à jour du ''program counter'', le chargement, l'étage de décodage, etc. L'amont est suivi par le chemin de données, qui est organisée en plusieurs voies, chacune formant ce qu'on appelle un '''aval''' (''back end''), ou encore une '''voie'''. Un aval correspond soit à une unité de calcul, soit à l'unité mémoire, soit à une unité pour les branchements. Toutes les voies partagent le banc de registre, que ce soit pour lire les opérandes ou enregistrer un résultat.
[[File:Pipeline avec un aval et un amont (back-end et front-end).png|centre|vignette|upright=1.5|Pipeline avec un aval et un amont (back-end et front-end).]]
===La spécialisation des avals===
La quasi-totalité des processeurs modernes dispose d'une voie séparée pour les accès mémoire, une '''unité mémoire''' dédiée. L'unité mémoire est séparée pour une bonne raison : les accès mémoire se marient assez mal avec un pipeline. Les accès mémoire ont une latence élevée, variable. Ils sont difficiles à pipeliner de manière générale. La voie pour les accès mémoire sert donc d'exception, c'est une unité qui n'est pas tout à fait pipelinée, alors que le reste du chemin de données l'est. Sa connexion avec le pipeline varie grandement suivant le processeur.
[[File:Pipeline à deux aval de type load-store.png|centre|vignette|upright=1.5|Pipeline à deux aval de type load-store.]]
Si on omet les voies liées aux accès mémoire, les autres voies correspondent peu ou prou à une unité de calcul. Par exemple, il est fréquent d'avoir des voies séparés pour l'addition, la multiplication et les décalages (''barrel shifter''), vu que ces opérations sont dans des ALU entières séparées. De même, on a une voie séparée pour les instructions flottantes, lié au fait que la FPU est une unité de calcul séparée.
La destination de chaque voie peut varier. Les instructions de calcul ou les lectures enregistrent un résultat dans les registres, et se terminent donc sur le port d'écriture du banc de registres. Par contre, les voies pour les branchements ou les écritures n'enregistrent rien dans les registres. La voie pour les branchements se termine dans le séquenceur et/ou dans le registre d'état, la voie pour les écritures ne finit nulle part si ce n'est dans l'unité mémoire.
[[File:Pipeline avec un aval pour les instructions arithmétiques, un aval pour les accès mémoire et un aval pour les branchements.png|centre|vignette|upright=1.5|Pipeline avec un aval pour les instructions arithmétiques, un aval pour les accès mémoire et un aval pour les branchements]]
Plus tard dans la suite du cours, nous verrons que les processeurs modernes regroupent plusieurs ALUs par voie, pour des raisons assez compliquées à expliquer ici. Retenez juste que le chemin de données est composé de plusieurs voies relativement indépendantes, chacune spécialisée dans un type de micro-opération spécialisé. Les voies commencent toutes avec la lecture des opérandes, elles effectuent un calcul, puis enregistrent leurs résultats dans les registres si besoin.
===Les processeurs à émission simple et multiple===
Toujours est-il que le décodeur envoie des instructions décodées dans la voie appropriée. Par exemple, une instruction d'accès mémoire va dans l'unité mémoire, dans la voie spécialisée dans les accès mémoire. Une instruction de calcul va dans la voie spécialisée dans les calculs entiers simples, à savoir l'ALU entière. Et ainsi de suite. L'envoi d'une instruction/micro-opération dans une voie s'appelle l''''émission'''. On dit qu'une instruction est émise quand elle est envoyée dans une voie.
Sur les processeurs dits à '''simple émission''', une instruction est envoyée dans une voie à chaque cycle, soit dans une ALU, soit dans l'unité mémoire, soit dans les voies pour les branchements, etc. Ils peuvent émettre au maximum une instruction apr cycle d'horloge, ils exécutent en moyenne une instruction par cycle. Diverses optimisations comme le renommage de registres et l'exécution dans le désordre permettent de se rapprocher de ce rythme de croisière d'une instruction lancée par cycle.
Mais il existe des processeurs à '''émission multiple''' qui sont capables d'émettre plusieurs instructions à la fois, chacune dans des voies séparées. Les processeurs dits superscalaires sont dans ce cas, mais ils ne sont pas les seuls. Ils permettent de gagner en performance en utilisant au maximum les unités de calcul/voies. Si plusieurs unités de calcul sont inoccupées, un processeur à émission multiple pourra lancer une instruction dans chacune d'entre elles en même temps, ce qui permet de les occuper le plus vite possible. De moins, c'est le cas si les conditions adéquates sont réunies, mais laissons cela pour plus tard. Un chapitre entier sera dédié aux processeurs à émission multiple.
==Les dépendances structurelles : l'occupation de chaque voie==
Les pipelines multi-cycles doivent gérer des '''dépendances structurelles''', à savoir le cas où deux instructions veulent utiliser le même circuit en même temps. Les dépendances structurelles sont généralement assez rares, car les pipelines prennent bien garde à bien séparer les étages du pipeline, en dupliquant des circuits s'il le faut. Par exemple, séparer le cache en un cache d'instruction et un cache de donnée élimine une dépendance structurelle. Mais sur les pipeline multi-cycles, c'est une autre histoire. Mais pour comprendre pourquoi, il faut voir quelles sont les sources de ces dépendances. Pour résumer, elles se situent au niveau des unités de calcul et du banc de registre.
===Les dépendances structurelles liées aux unités de calcul===
En théorie, une fois qu'une instruction est décodée, elle est envoyée dans la voie adaptée. Du moins, si cette voie est libre. En effet, l'usage d'instructions multicycles fait qu'une voie peut être occupée pendant plusieurs cycles. Un exemple est celui où on veut faire deux multiplications à la suite, en supposant qu'une multiplication fasse 5 cycles : la seconde doit attendre que la première se termine car le multiplieur est occupé pendant ce temps. Et en général, des instructions consécutives ne peuvent pas utiliser la même voie, la même ALU. La voie sera occupée par la première instruction, les autres devront attendre que la première ait terminé ses calculs.
En théorie, il est possible d'éliminer totalement ces dépendances structurelles. Une solution simple duplique l'unité de calcul fautive. Prenons un exemple où c'est le circuit multiplieur qui est fautif, où la multiplication prend cinq cycles sur le processeur. Avec une seule ALU, on doit attendre qu'une multiplication en cours soit terminée avant de lancer la suivante. Mais il est possible de lancer une nouvelle multiplication à chaque cycle si on dispose de 5 circuits multiplieurs : on lance une nouvelle multiplication à chaque cycle dans un multiplier différent. Mais le cout en transistors est prohibitif, surtout que le gain en performance est généralement faible. Il est en effet rare que 5 multiplications soient lancées à la suite dans le pipeline, ce qui fait que si on utilisait 5 multiplieurs, ils seraient sous-utilisés en pratique.
La seconde solution pipeline, échelonner les unités de calcul. Si jamais vous avez une opération qui prend cinq cycles, pourquoi ne pas lui fournir un seul circuit échelonné en cinq étages ? Pour certains circuits, c'est possible : on peut totalement échelonner une unité de multiplication, par exemple. En faisant ainsi, il est possible de démarrer une nouvelle multiplication à chaque cycle d'horloge dans la même ALU, éliminant ainsi toute dépendance structurelle.
[[File:Exemple avec une unité de multiplication séparée, totalement échelonnée.png|centre|vignette|upright=2|Exemple avec une unité de multiplication séparée, totalement pipelinée.]]
Dans la réalité, les statistiques montrent qu'il est rare que deux instructions multicycles se suivent dans un programme. La seule exception étant les instructions mémoire, mais nous les mettons à part pour le moment. En conséquence, les dépendances structurelles ont un cout en performance assez mineur. Échelonner un circuit permet un gain de performance pour un cout en circuit modéré : il suffit d'ajouter quelques registres dans le circuit. Le problème est que certaines instructions s’échelonnent mal, notamment les divisions. Aussi, de nombreux concepteurs de processeurs laissent quelques dépendances structurelles. Nous verrons comment elles sont gérées au chapitre suivant.
===Les dépendances structurelles à l'écriture dans le banc de registre===
Les instructions multi-cycle lisent et écrivent dans le même banc de registres que les instructions 1-cycle. Le résultat est qu'elles peuvent écrire dans les registres en même temps. Par exemple, imaginons que l'on exécute une multiplication de 6 cycles, suivie au cycle suivant par une opération qui n'en prend que 5. Elles tenteront d'enregistrer leurs données en même temps 6 cycles après pour la première, 1 + 5 cycles pour l'autre. Elles voudront donc enregistre une donnée en même temps. Mais le banc de registre n'a qu'un seul port d'écriture...
La solution la plus simple est que la seconde instruction doit être retardée d'un cycle pour résoudre le problème. En soi, rien de problématique à gérer, l'unité de décodage a juste à insérer des bulles de pipeline. Une autre solution ajoute des ports d'écriture sur le banc de registres, afin que les deux instructions peuvent écrire en même temps dans le banc de registre. Il faut juste qu'elles écrivent dans des registres différents, mais il s'agit d'un cas de dépendance de données, pas une dépendance structurelle proprement dit.
La première solution a un cout en performance, mais est simple à implémenter. Son cout en transistor est limité, tout est concentré dans l'unité de décodage/émission. Pour la seconde solution, son cout en transistor est important, sans compter que le banc de registre devient alors plus gourmand en énergie. Le tout est à comparer à un gain en performance généralement limité. La solution est surtout utilisée sur les processeurs avec beaucoup d'unités de calcul.
Les premiers processeurs MIPS avaient une technique très particulière pour éliminer les dépendances structurelles de ce genre, qui éliminait aussi partiellement les dépendances WAW. Ils utilisaient un pipeline RISC classique modifié pour supporter des instructions multi-cycles, à savoir des multiplications et éventuellement les divisions. Ils ajoutaient un aval séparé, un simple circuit multiplieur/diviseur séparé de l'ALU entière. Les instructions multi-cycles disposaient de leur propre set de registres rien que pour elles, ce qui résolvait pas mal de problèmes de conflits d'accès aux registres.
==L'inversion de l'ordre des écritures==
Un problème des pipelines dynamiques est qu'ils ne garantissent pas l'ordre des écritures. Si on émet des instructions dans l'ordre du programme, il se peut qu'elles ne terminent pas dans le même ordre. Par exemple, prenons l'exemple d'une multiplication prenant 6 cycles, suivie par une addition s’exécutant en un cycle. Le résultat est illustré ci-dessous : l'ordre d'enregistrement des résultats des instructions s'est inversé. Les instructions sont émises une par une, dans l'ordre, mais l'enregistrement des résultats dans les registres se font dans le désordre si elles n'ont pas de dépendances entre elles. On dit qu'il y a '''inversion de l'ordre des écritures'''.
[[File:Exemple de problème survenant avec des pipeline de longueur variable.png|centre|vignette|upright=2|Exemple de problème survenant avec des pipeline de longueur variable]]
L'inversion de l'ordre des écritures pose problème pour supporter les exceptions précises. Il est possible qu'une instruction lève une exception alors qu'une instruction suivante a déjà enregistré son résultat. Imaginez par exemple qu'une multiplication soit suivie par une addition. Imaginez que la multiplication lève une exception matérielle du type "débordement d'entier", 3 cycles après avoir démarré. L'addition aura déjà enregistré son résultat, vu qu'elle se fait en un seul cycle...
Au-delà des problèmes liés aux exceptions précises, les dépendances de données posent aussi problème. Si deux écritures se font dans des registres différents, peu importe l'ordre des écritures, tout se passera bien. Mais si les deux écritures se font dans le même registre, il y a un problème : le résultat est incorrect, un registre a été écrasé avec une valeur antérieure. Le problème en question correspond à l'apparition de '''dépendances de donnée de type WAW'''. Les dépendances WAW imposent un ordre concernant les écritures dans les registres et posent des problèmes sur les pipelines dynamiques, uniquement eux.
===Les registres d'échelonnage===
Pour conserver l'ordre des écritures, la solution la plus simple est de '''retarder les écritures''' jusqu'à ce qu'elles atteignent la toute fin du pipeline. Ainsi, il n'y a pas d'inversion d'ordre des écritures dans les registres, ni d'écritures simultanées. L'implémentation est plus simple si on suppose que toutes les instructions font le même nombre de cycles. On parle alors d'un pipeline de longueur fixe. Le cout à payer est que les instructions doivent se caler sur l'instruction la plus lente. Si une addition met un cycle à s'exécuter et une multiplication 3, alors toutes les instructions font 3 cycles.
Si l'addition fournit son résultat en un cycle, il sera enregistré dans les registres avec un retard de deux cycles. Idem pour les lectures mémoire : si un accès mémoire fait 2 cycles, l'écriture de la donnée lue dans les registres sera retardée d'un cycle. Le retard dépend de l’opération à effectuer, en fonction de combien de cycles elle prend dans l'ALU. En clair, toutes les instructions ont le même nombre d'étages, mais certains étages sont inutiles pour certaines instructions. L'instruction doit passer par ces étages, mais ceux-ci ne doivent rien faire.
[[File:Processeur à pipeline fixe qui gére des instructions multicycles.png|centre|vignette|upright=3|Processeur à pipeline fixe qui gére des instructions multicycles]]
Reste à ajouter des cycles de retard, qui servent juste à retarder l'enregistrement dans les registres. Pour cela, on insère des registres entre la sortie de l'ALu et le banc de registre. Les registres en question sont appelés des ''staggering registers'', ou encore des '''registres d'échelonnage'''. Il y a autant de registres d'échelonnage que cycles de retard à ajouter, car chacun retarde le résultat d'un cycle d’horloge. Pendant les cycles de retard, le résultat sera passé d'un registre d'échelonnage à l'autre, sans que rien ne se passe.
[[File:Regsitres d'échelonnage.png|centre|vignette|upright=2|Registres d'échelonnage]]
Un défaut de la méthode précédente est que les données sont copiées d'un registre à l'autre à chaque cycle d'horloge, ce qui consomme de l'énergie pour rien. Pour éviter cela, les processeurs modernes regroupent les registres d'échelonnage dans un banc de registre séparé, appelé le '''banc de registres d'échelonnage'''. Lors du décodage de l'instruction, un registre est attribué à l'instruction pour mémoriser son résultat, si besoin. Le résultat est enregistré dans le registre alloué en sortant de l'unité de calcul. Puis, il est copié dans le banc de registre architectural dès que le délai nécessaire est écoulé.
[[File:Banc de registres d'échelonnage.png|centre|vignette|upright=2|Banc de registres d'échelonnage]]
Il faut rappeler que pour les écritures en RAM, le processeur utilise une file d'écriture pour garantir que les écritures se font dans l'ordre. Nous en avions parlé dans le chapitre sur le pipeline, quand nous avions parlé des exceptions précises. On peut la voir comme une sorte d'équivalent des registres d'échelonnage, mais pour les écritures en mémoire. La file d'écriture est séparée des registres d'échelonnage, pour une raison simple : elle ne communique pas avec les registres, mais avec le cache. Sa sortie étant différente, elle est donc séparée.
===Le tampon de réordonnancement===
Le '''tampon de réordonnancement''' est la technique phare utilisée pour remettre les écritures dans l'ordre. L'idée est là encore de remettre les écritures dans l'ordre. Les micro-opérations fournissent leurs résultats dans le désordre, mais les résultats sont mis en attente, afin que les écritures se fassent dans l'ordre. La mise en attente se fait dans une mémoire appelée le tampon de ré-ordonnancement, ''Re-order buffer'' en anglais, ce qui fait que j'utiliserais parfois l'abréviation ROB dans ce qui suit.
La différence avec les registres d’échelonnement est que la mise en attente est plus flexible, elle ne demande pas que toutes les instructions prennent le même nombre de cycles pour s'exécuter. Une instruction peut enregistrer son résultat dès que les précédentes sont terminées, au cycle précis où elles ont toutes enregistrées leurs résultats. Dit autrement, seule l'instruction la plus ancienne peut quitter le ROB et enregistrer son résultat, les autres instructions doivent attendre. De ce point de vue, le tampon de réordonnancent est donc une mémoire FIFO. On peut la voir comme une amélioration de la technique précédente, où les registres d’échelonnement sont remplacés par une structure matérielle plus complexe.
[[File:Tampon de réordonnancement.png|centre|vignette|upright=2|Tampon de réordonnancement.]]
Les résultats sont enregistrés dans le désordre dans le ROB, mais finissent triés par l'ordre du programme. L'ordre d'écriture demande de mémoriser l'ordre d'exécution des instructions. Pour cela, on profite du fait que les instructions sont décodées dans l'ordre. Après les décodeurs se trouve un circuit appelé l'unité d'émission, qu'on détaillera dans le chapitre suivant. Pour simplifier, elle vérifie que l'instruction qu'elle reçoit peut s'exécuter, et l'envoie aux unités de calcul si c'est le cas. L'unité d'émission ajoute l'instruction au ROB quand elle l'envoie au chemin de données. Si le ROB est plein, on ne peut pas y ajouter de nouvelle instruction. En conséquence, le processeur bloque les étages de chargement, décodage, etc. Le processeur est débloqué quand une instruction enregistre ses résultats.
Un processeur avec un tampon de ré-ordonnancement est donc composé de trois sections : un ''front-end'' qui regroupe chargement et décodage, un chemin de données (ALU et registres), et un système complémentaire de ré-ordonnancement. Ce dernier est composé du tampon de ré-ordonnancement, de l'unité d'émission et de l'étage de ''writeback'' pour l'enregistrement dans les registres. Le ROB envoie toutes les informations nécessaires à l'étage de ''Writeback'', à savoir le registre de destination, l'indicateur d'exception, le résultat à enregistrer, etc.
[[File:Processeur avec un tampon de ré-ordonnancement.png|centre|vignette|upright=2|Processeur avec un tampon de ré-ordonnancement]]
Les résultats sont enregistrés dans le ROB dans le désordre. Cependant, le ROB fait le lien entre un résultat et l'instruction qui l'a produite, il associe une instruction avec son résultat. Expliquer comment est fait ce lien demande d'expliquer comment est implémenté le ROB. Pour simplifier, c'est un subtil mélange entre une mémoire cache et une mémoire FIFO. Le ROB est composé d''''entrées''', chacune contient de quoi faire le lien entre un résultat et l'instruction associée.
Une entrée contient le ''program counter'' associé à l'instruction, le registre de destination du résultat, un champ pour mémoriser le résultat, et un ''bit de présence''. Ce dernier indique si le résultat a bien été calculé. Le champ résultat est initialement laissé vide, mais le résultat de l'instruction est copié dedans une fois qu'il est disponible. Notons que certaines instructions ne renvoient pas de résultat, comme les branchements ou les écritures en mémoire, mais on leur alloue quand même une entrée dans le ROB. Pour cela, le bit de présence est couplé à un bit qui indique si l'instruction doit fournir un résultat ou non. Les deux sont regroupés sous le noms de bits de présence. Si le processeur gère des exceptions précises, il faut ajouter un champ pour l'indicateur d'exception, qui est utilisé pour gérer des exceptions précises.
{|class="wikitable"
|+ Une entrée du ROB
|-
| ''Program counter'' || Registre de destination || Champ résultat || Bits de présence || Champ exception
|}
Lorsqu'une instruction est émise, elle réserve une entrée du ROB, pour que son résultat soit ajouté dedans ultérieurement. L'émission se faisant dans l'ordre, les entrées du ROB sont allouées dans l'ordre. Le ROB est donc une sorte de mémoire FIFO du point de vue des instructions. Une instruction réserve une entrée du ROB en écrivant son ''Program counter'' dedans, et en remplissant le champ pour le registre de destination. Le champ
Par la suite, les différents résultats vont être enregistrés dans les entrées adéquates, dans l'entrée réservée par l'instruction. Pour faire le lien entre un résultat et une instruction, le ROB fonctionne comme un cache d'instruction dont le ''tag'' serait le ''program counter''. Le ''program counter'' est propagé dans le pipeline, d'étage en étage, ce qui fait que le résultat sortira de l'unité de calcul en étant associé à ce ''program counter''. Le ''program counter'' est alors utilisé pour accéder au ROB comme on le ferait avec un cache d'instruction. L'entrée qui déclenche un succès de cache est alors sélectionnée : le résultat est copié dedans.
Une fois que toutes les instructions précédentes sont terminées, l'instruction peut enregistrer son résultat dans les registres. instruction quitte alors le ROB, dans le sens où l'entrée est vidée. Le résultat à enregistrer est lu depuis le champ résultat, le registre de destination est lu depuis le champ du même nom. L'étage de ''Writeback'' vérifie que le résultat est disponible en vérifiant les bits de présence. L'étage de ''writeback'' peut aussi vérifier si l'instruction a levé une exception matérielle, en regardant le champ Exception. Si une exception a lieu, cela signifie que les instructions suivantes sont invalides. Le ROB est alors vidé, ce qui le débarrasse des instruction invalides : leurs résultats ne seront pas enregistrés dans les registres architecturaux. Le même mécanisme est utilisé pour gérer les mauvaises prédictions de branchement.
===Le tampon d’historique===
Une autre solution laisse les instructions écrire dans les registres dans l'ordre qu'elles veulent, mais conserve des informations pour remettre les écritures dans l'ordre, pour retrouver les valeurs antérieures. Ces informations sont stockées dans ce qu'on appelle le '''tampon d’historique''' (''history buffer'' ou HB).
Le tampon d'historique est une mémoire LIFO dont chaque mot mémoire est une entrée qui mémorise les informations dédiées à une instruction. Lorsqu'une instruction modifie un registre, le HB sauvegarde une copie de l'ancienne valeur, pour la restaurer en cas d'exception. Pour annuler les modifications faites par des instructions exécutées à tort, on utilise le contenu de l'HB pour remettre les registres à leur ancienne valeur. Plus précisément, on vide le HB dans l'ordre inverse d'ajout des instructions, en allant de la plus récente à la plus ancienne, jusqu'à vider totalement le HB. Une fois le tout terminé, on retrouve bien les registres tels qu'ils étaient avant l’exécution de l'exception.
[[File:Tampon d’historique.png|centre|vignette|upright=2|Tampon d’historique.]]
===Le banc de registres futurs===
Le ROB et le HB sont deux techniques opposées sur le principe. Le ROB part du principe assez pessimiste que le banc de registre doit conserver un état propre, capable de gérer des exceptions précises. L'état temporaire est alors stocké dans le ROB, afin de pouvoir être annulé en cas de souci. La récupération en cas d'exception/branchement est alors assez rapide, mais le cout d'implémentation est assez important. Le HB fait l'inverse, avec une technique optimiste. Le HB enregistre directement l'état temporaire dans le banc de registre, mais mémorise de quoi revenir en arrière. Avec un HB, remettre les registres à l'état normal prend du temps. Deux techniques assez opposées, donc.
Il existe une solution intermédiaire, qui consiste à utiliser à la fois un ROB et un HB. La technique utilise deux bancs de registres. Le premier est mis à jour comme si les exceptions n’existaient pas, et conserve un état spéculatif : c'est le '''banc de registres futurs''' (future file ou FF). Il fonctionne plus ou moins comme l'''History Buffer'' de la section précédente. L'autre stocke les données valides en cas d'exception : c'est le '''banc de registres de retrait''' (retirement register file ou RRF). Il fonctionne sur le même principe que le banc de registre avec un ROB. Il est d'ailleurs couplé à un ROB, histoire de conserver un état valide en cas d'exception. Mais ce ROB est simplifié, comme on va le voir dans ce qui suit.
Le FF est systématiquement utilisé pour l'exécution des instructions. Dès qu'on doit exécuter des instructions, c'est dans ce banc de registre que sont lues les opérandes. La raison est que ce banc de registre contient les dernières données calculées. Notons que dans la technique utilisant seulement un ROB, les opérandes auraient été lues depuis le ROB. Mais là, le ROB n'est plus connecté aux entrées de l'ALU, seul le FF l'est. Le câblage est donc plus simple et l'implémentation facilitée. Le RRF est quant à lui utilisé en cas d'exception ou de branchement, pour récupérer l'état correct.
Après une exception ou un branchement, deux méthodes sont possibles. Avec la première, les opérandes sont lues depuis ce banc de registre, jusqu'à ce que le FF soit de nouveau utilisable. Mais détecter quel banc de registre utiliser est assez compliqué. Elle n'est en pratique pas implémentée, car demandant trop de circuits. L'autre solution est de recopier le contenu du RRF dans le FF. Là encore, le temps de recopie est assez long, sauf si on utilise certaines optimisations des bancs de registre. Il existe en effet des méthodes pour copier un banc de registre entier dans un autre en à peine un ou deux cycles d'horloge. Elles sont assez compliquées et on ne peut pas les expliquer ici simplement.*
[[File:Banc de registres futurs.png|centre|vignette|upright=2|Banc de registres futurs.]]
Une variante de cette technique a été utilisée sur le processeur Pentium 4. La différence avec la technique présentée est l'usage du renommage de registre, qui permettait de se passer de deux bancs de registres proprement dit. Les deux bancs de registres étaient inclus dans un banc de registre beaucoup plus grand. Mais nous détaillerons cela dans quelques chapitres.
===Les point de contrôle de registre===
La technique du '''''register checkpointing''''' est une technique qui marche surtout pour les branchements, mais ne gére pas les exceptions précises, du moins pas dans sa version la plus simple. Elle peut être adaptée pour gérer des exceptions précise, mais nous allons simplement voir une version qui gère uniquement les branchements.
Elle consiste à sauvegarder les registres quand un branchement est décodé, pour ensuite restaurer les registres si besoin. Tous les registres architecturaux du processeur sont sauvegardés, et parfois quelques registres microarchitecturaux. En clair, une copie intégrale du banc de registre est réalisée, le registre d'état est lui aussi sauvegardé, etc. La sauvegarde des registres porte le nom de '''point de contrôle de registre''', nous dirons simplement "point de contrôle". Le point de contrôle est stocké dans un autre banc de registre, séparé du banc de registre principal, qui est complétement invisible pour le programmeur.
Un point de contrôle est pris au moment où un branchement est décodé. On ne sait pas si ce branchement sera pris ou non, ce qui fait les instructions qui vont suivre peuvent être correcte si le branchement n'est pas pris, ou incorrectes si le branchement est pris et saute ailleurs dans le programme. Le branchement s'exécute normalement, le banc de registre est modifié par les instructions qui le suivent, tout comme c'est le cas avec un HB. Vers la fin du pipeline, on regarde si le branchement est pris ou non. S'il n'est pas pris, il n'y a rien à faire : les registres contiennent des données valides. Mais si le branchement est pris, alors les registres contiennent des valeurs invalides. Le point de contrôle est restauré, à savoir que le banc de registre est restauré dans le même état qu'un moment du point de sauvegarde.
[[File:Register checkpointing.png|centre|vignette|upright=1.5|Register checkpointing]]
Il est intéressant de comparer cette technique à la technique du tampon d'historique. Le principe est le même : on laisse les instructions modifier les registres, mais on doit annuler ces modifications en revenant en arrière. Sauf que le tampon d'historique restaure les registres un par un. Alors qu'avec un point de contrôle, la restauration du banc de registre se fait en bloc : on restaure tous les registres d'un seul coup, en un seul cycle. Une autre différence est que le point de contrôle ne s’embarrasse pas à savoir quels registres ont été modifiés à tord ou non, tous les registres sont sauvegardés et restaurés en un ou deux cycles d'horloge. Bien sûr, cela demande des bancs de registre spécialement conçus pour. Tout se passe comme si le tampon d'historique étant remplacé par un second banc de registre, avec une procédure de restauration optimisée se faisant en bloc.
La technique peut aussi être comparée avec la technique du banc de registres futurs, elle-même très liée au tampon d'historique. L'idée est que le point de contrôle est le RRF (banc de registre de retirement), alors que le banc de registre normal est un FF (banc de registre futur). La différence est que le point de contrôle fait qu'on n'a pas besoin de savoir quelles sont les modifications à annuler ou non. Le banc de registre tout entier est restauré, pas seulement les registres modifiés à tord. Une autre différence est qu'avec un banc de registre futur, le RFF est mis à jour à chaque cycle d'horloge, dès qu'une instruction peut enregistrer ses résultats. Le point de contrôle n'est pas mis à jour mais est pris en une fois, en un seul cycle d'horloge, et ne change plus après. En conséquence, il n'y a pas besoin de ROB pour gérer l'état du RRF.
===La complétion dans le désordre===
Les techniques précédentes remettent dans l'ordre les écritures dans les registres, afin de gérer les branchements et exceptions. Mais elles s'appliquent sur toutes les instructions, même en absence de branchements. Mais diverses optimisations permettent de contourner ces techniques ou de les désactiver dans des conditions bien précises, pour gagner en performance, tout en garantissant que cela n'ait pas de conséquences sur l'exécution du programme. Il s'agit de techniques dites de '''complétion dans le désordre'''.
En général, elles s'appliquent en absence de branchements ou quand le processeur sait qu'aucune exception ne peut survenir. La remise en ordre des écritures est alors mise en pause et les écritures se font dans le désordre. Les écritures sont faites en avance, alors que des instructions précédentes ne sont pas terminées. Par valider des écritures en avance, on veut parler de mettre à jour les bancs de registre, qu'il s'agisse du ''retirement register file'', du point de contrôle, ou toute autre structure des techniques précédentes. Rappelons que le ''scoreboard'' ou l'unité d'émission s'arrange pour l'exécution dans le désordre ne change pas le comportement du programme exécuté.
Un exemple est celui du processeur ARM Cortex 73, qui dispose d'un tel mécanisme. Il s’applique en absence de branchements et peut être vu comme une amélioration des lectures non-bloquantes. Le mécanisme s'active quand une lecture est bloquée par un défaut de cache ou toute autre situation. Le processeur implémente l'exécution dans le désordre, ce qui fait qu'il exécute les instructions qui suivent une lecture bloquée, à condition qu'elles ne dépendent pas de la lecture. L'idée est alors de laisser ces instructions écrire dans le banc de registre, alors que la lecture précédente est encore en attente. Il faut cependant que la lecture soit arrivée à un certain état d'avancement pour que le processeur autorise ces écritures : il faut garantir que la lecture ne déclenchera pas un défaut de page ou toute autre exception matérielle. Mais une fois que le processeur sait que la situation est OK, il autorise les instructions suivant la lecture enregistrer leurs résultats pour de bon.
Le processeur ARM Cortex 73 en question ne dispose apparemment pas de ROB, mais il doit certainement avoir une structure similaire pour garantir l'ordre des écritures quand un branchement est présent. L'avantage de la technique est qu'elle permet à certaines instructions de finir en avance, ce qui libère de la place dans le ROB, le tampon d'historique, ou toute autre structure matérielle qui met en attente les écritures. Elles permet d'avoir de meilleures performances sans augmenter la taille de ces structures, ou bien d'obtenir des performances similaires à cout en circuits réduit. Le CPU ARM Cortex 73 a un budget en transistor assez restreint, ce qui fait que cette optimisation prend tout son sens sur ce CPU.
==La fréquence des avals des pipelines multicycle==
L'usage d'un pipeline complexe , qui sépare amont et avals, permet de nombreuses optimisations. Par exemple, il est possible de faire fonctionner certains avals à une fréquence supérieure aux autres. Typiquement, on peut faire fonctionner l'unité de calcul flottante à une fréquence inférieure des unités de calcul entières, afin d'économiser un peu d'énergie. Ou encore, certaines ALU peuvent fonctionner à une fréquence double de celle du processeur, afin de gagner en performance. Voyons un petit peu quelles sont ces optimisations.
===Le ''clock gating'' des avals inutilisés===
Afin de réduire la consommation d'énergie du processeur, les avals inutilisés peuvent être désactivés, mis en veille. La technique du ''clock gating'' vue dans le chapitre sur la consommation électrique des circuits, coupe le signal d'horloge pour les unités de calcul inutilisées. Mais il est aussi possible de couper l'alimentation, ce qui porte le nom de ''power gating''.
Les processeurs avec un pipeline simple bloquent dès qu'un défaut de cache est rencontré, à savoir qu'ils émettent des bulles de pipeline tant que la RAM n'a pas répondu. Lors d'un défaut de cache, il est possible de désactiver les unités de calcul et les registres en attendant que la RAM réponde. Les registres sont réactivés juste avant que la donnée n'arrive. Les gains sont d'autant plus grands que les accès mémoires sont longs, mais il faut avouer que ce n'est pas l'exemple le plus crédible. Les processeurs modernes disposent d'optimisations, comme les lectures non-bloquantes ou l'exécution dans le désordre, qui permettent d'exécuter des calculs pendant un défaut de cache.
Un autre exemple, bien plus intéressant, se base sur une observation assez intéressante : il est très rare qu'un programme entrelace des instructions flottantes et entières. En conséquence, il est possible de désactiver la FPU et le banc de registres flottants si elles sont inutilisées. Et il est aussi possible de désactiver l'ALU entière et les registres entiers pendant les instructions de calcul flottant. Les instructions flottantes étant assez longues, généralement une dizaine de cycles, voire plus, désactiver l'ALU et les registres entiers permet d'économiser pas mal d'énergie. Et vu qu'une instruction flottante vient rarement seule, l'ALU et les registres entiers sont généralement désactivés pendant une centaine/milliers de cycles d'horloge.
La désactivation des unités inutilisée est commandée par l'unité de contrôle. Une fois qu'elle a décodée l'instruction, elle sait quelles unités sont nécessaires pour exécuter l'instruction et quelles sont celles inutilisées. L'unité de contrôle a juste à envoyer des signaux de commande supplémentaires aux circuits de ''clock gating''. Les gains peuvent être substantiels. Par exemple, pour le processeur Power 5, IBM a déclaré que le ''clock gating'' lui permettait d'économiser 25% d'énergie.
===Les unités de calcul à double fréquence du Pentium 4===
Le Pentium 4 était un peu particulier dans son genre, avec une ALU à mi-chemin entre une ALU normale et une ALU bit-slicée. Il disposait de plusieurs unités de calcul sur les nombres entiers, dont l'une d'entre elle était une ALU simple qui ne gérait que les additions, les soustractions, les opérations logiques et les comparaisons. Les multiplications et décalages étaient gérés par une ALU séparée. Il y avait donc une ALU simple à côté d'une ALU complexe.
L'ALU simple était composée de deux sous-ALU de 16 bits chacune. La première envoyait le bit de retenue qu'elle a calculée à la seconde. Un point important est que l'ALU prenait deux cycles d'horloge pour faire son travail : le premier cycle calculait les 16 bits de poids faible dans la première sous-ALU, puis calculait les 16 bits de poids fort lors du second cycle (il y avait aussi un troisième cycle pour le calcul des drapeaux du registre d'état, mais passons). Le tout est appelé '''addition étagée''' (''staggered add'') dans la documentation Intel.
Et la magie était que l'unité de calcul fonctionnait à une fréquence double de celle du processeur ! Pour faire la différence entre les deux fréquences, nous parlerons de fréquence/cycle processeur et de fréquence/cycle de l'ALU. Le résultat de ce fonctionnement franchement bizarre, est que les 16 bits de poids faible étaient calculés en une moitié de cycle processeur, alors que l'opération complète prenait un cycle. L'utilité devient évidente quand on sait que l'ALU simple était utilisée pour les calculs d'adresse. L'accès à la mémoire cache intégrée au processeur a besoin des bits de poids faible de l'adresse en priorité, les bits de poids fort étant nécessaires plus tard lors de l'accès. Calculer les bits de poids faibles d'une adresse en avance permettait d'accélérer les accès au cache de quelques cycles.
La technique en question porte le nom barbare d''''ALU ''double pumped''''', dont une traduction naïve ne donne pas un terme français très parlant. L'idéal est de la parler d'ALU à double fréquence. Il peut exister des ALU à triple ou quadruple fréquence, mais ce n'est pas très utilisé. Il faut noter que certains processeurs autres que le Pentium 4 utilisent cette technique, mais nous en reparlerons quand nous serons au chapitre sur les processeurs SIMD.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les optimisations du chargement des instructions
| prevText=Les optimisations du chargement des instructions
| next=L'émission dans l'ordre des instructions
| nextText=L'émission dans l'ordre des instructions
}}
</noinclude>
qqfopiydldouzvvbr4tnzm4ro6of6ge
Fonctionnement d'un ordinateur/Les processeurs superscalaires
0
65956
771982
765332
2026-09-11T14:30:36Z
Mewtow
31375
/* Les décodeurs d'instructions superscalaires */
771982
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 n'ont pas été implémentées.
La macro-fusion est utilisée pour fusionner un test et un saut en un branchement unique. Une seconde utilisation, proposée sur le jeu d'instruction RISC-V, fusionnait un calcul d'adresse et une instruction LOAD/STORE en une seule instruction d'accès mémoire. Cette seconde utilisation a été envisagée à une époque où le jeu d'instruction RISC-V ne gérait pas les modes d'adressage base + indice. L'idée était d'avoir un pipeline mémoire qui gérait le mode d'adressage base + indice et de fusionner des instructions pour l'exploiter. Mais de nos jours, le jeu d'instruction RISC-V intégre le mode d'adressage base + indice, ce qui fait que la macro-fusion n'a aucun intérêt pour ce cas précis.
La macro-fusion a de nombreuses limitations en pratique. En théorie, on pourrait l'utiliser au-delà des deux situations mentionnées plus haut. Par exemple, pour fusionner une addition et une multiplication en une seule instruction MAD. Mais l'intérêt est assez faible. Notons que la macro-fusion ne permet que de fusionner des paires d'instructions. Aller au-delà demanderait un cout en circuit trop important. Déjà que tester deux paires utilise pas mal de comparateurs...
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. À 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>
n4z8y38twzvefd4awj8a59euxkt4li4
Fonctionnement d'un ordinateur/Le chemin de données
0
69025
772057
768597
2026-09-11T23:11:50Z
Mewtow
31375
/* L'unité de calcul d'adresse */
772057
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
: Rappelons que dans ce chapitre, ainsi que dans les quatre chapitres suivants, nous ne parlons que des architectures à registres généraux. Aussi, nous mettons de côté les anciens processeurs à accumulateur et les CPU 8 bits, commercialisés avant l'arrivée des registres généraux, ainsi que certaines architectures non-conventionnelles comme les DSPs, qui ont des unités de calcul d'adresse dédiées. Mais nous ne parlerons pas de celles-ci dans ce chapitre.
L'AGU est utilisée pour certains modes d'adressage, à savoir ceux qui demandent de combiner une adresse avec soit un indice, soit un décalage, plus rarement les deux. Les calculs d'adresse demandent d'incrémenter/décrémenter une adresse, de lui ajouter un indice ou un décalage, de décaler l'indice, guère plus. Pas besoin de multiplications, de divisions, ou d'autres opération plus complexe : décalages et additions/soustractions suffisent. L'AGU est donc beaucoup plus simple qu'une ALU normale et se résume à un additionneur-soustracteur couplé à un décaleur.
Mais pourquoi ajouter une AGU, alors que l'ALU entière suffit largement pour implémenter les modes d'adressage complexes. Il est parfaitement possible de faire les calculs d'adresse dans l'ALU entière, puis d'envoyer le résultat à l'unité mémoire. Sauf que faire ainsi demande de câbler un chemin reliant la sortie de l'ALU entière à l'unité mémoire. Rien d'impossible, on verra même cela en détail à la fin du chapitre. Le problème est que cette connexion est souvent difficile à câbler. An ajoutant une AGU, cette liaison disparait, l'unité mémoire n'est reliée qu'aux registres. Le cablage du processeur est simplifié, au prix d'un cout modéré en transistors.
Une des rares exceptions est le mode d'adressage "Base + Indice + Décalage", qui additionne trois opérandes, chose que l'ALU entière ne sait pas faire (sauf exceptions). Les processeurs x86 supportent ce mode d'adressage, qui est rarement utilisé. Pour l'accélérer, les premiers processeurs Intel supportaient ce mode d'adressage, grâce à un additionneur séparé de l'ALU, qui servait en pratique d'AGU. Le calcul de l'adresse se faisait en deux cycles d'horloge. Lors du premier cycle d'horloge, l'ALU entière faisait le calcul d'adresse "Base + Indice". Lors du second cycle, le résultat de l'addition précédente est additionné avec le décalage, dans l'AGU. L'avantage est que le câblage du processeur est très simple. L'AGU était reliée à l'ALU entière et à l'unité de contrôle. C'est cette dernière qui extrait le décalage de l'instruction.
Une solution plus efficace effectue le calcul d'adresse avec un additionneur ''carry save'', capable d'additionner trois opérandes à la fois. L'AGU est alors implémentée avec un circuit décaleur, pour calibrer l'indice, et cet additionneur trois-opérandes. Le résultat est que l'on peut faire les calculs d'adresse en un seul cycle d'horloge, pour un cout en hardware modéré. Par contre, il faut relier l'unité de calcul au séquenceur, mais aussi aux registres.
[[File:Unité de calcul d'adresse.png|centre|vignette|upright=2|Unité de calcul d'adresse]]
===Le rafraichissement mémoire optimisé et le contrôleur mémoire intégré===
Depuis les années 80, les processeurs sont souvent combinés avec une mémoire principale de type DRAM. De telles mémoires doivent être rafraichies régulièrement pour ne pas perdre de données. Le rafraichissement se fait généralement adresse par adresse, ou ligne par ligne (les lignes sont des super-bytes internes à la DRAM). Le rafraichissement est en théorie géré par le contrôleur mémoire installé sur la carte mère. Mais au tout début de l'informatique, du temps des processeurs 8 bits, le rafraichissement mémoire était géré directement par le processeur.
Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le processeur 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''. Et tout cela était intégré à l'unité d'accès mémoire.
Depuis les années 2000, les processeurs modernes ont un contrôleur mémoire DRAM intégré directement dans le processeur. Ce qui fait qu'ils gèrent non seulement le rafraichissement, mais aussi d'autres fonctions bien pus complexes.
===L'interface de l'unité mémoire===
Vu de l'extérieur, l'unité mémoire ressemble à n'importe quel circuit électronique, avec des entrées et des sorties. L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie. Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. Si l'unité mémoire incorpore une AGU, on envoie aussi les indices et autres données sur le port d'entrée. Le port de sortie est utilisé pour récupérer le résultat des lectures. Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues.
L'unité mémoire est connectée au reste du processeur grâce à un réseau d'interconnexion qu'on étudiera plus loin. Les connexions principales sont celles avec les registres : les adresses à lire/écrire sont souvent lues depuis les registres, les lectures copient une donnée dans un registre. Il peut y avoir une connexion avec l'unité de calcul pour les opérations ''load-up'', ou pour le calcul d'adresse, mais c'est secondaire.
[[File:Unité d'accès mémoire LOAD-STORE.png|centre|vignette|upright=2|Unité d'accès mémoire LOAD-STORE.]]
===Un exemple : l'unité mémoire du 8086 et ses dérivés===
Après avoir vu la théorie, voyons un exemple du monde réel. Nous allons étudier l'exemple du processeur Intel 8086 et de son dérivé, le 80186. Ces processeurs avaient une particularité : leur unité mémoire était fortement séparée du reste du processeur. Le processeur était séparé en une ''Bus Interface Unit'' et une ''Execution Unit''. L'''Execution Unit'' a un nom trompeur : c'est l'ensemble du processeur, unité mémoire exclue. La ''Bus Interface Unit'' est l'unité mémoire. Elle se charge à la fois du chargement des données et des instructions.
Cependant, nous n'allons pouvoir détailler cette unité dans le détail, car nous n'avons pas encore vu la mémoire virtuelle, ni le préchargement. Mais cela ne nous gênera pas, vous aurez simplement quelques détails passés sous silence pour ce qui est de l'unité de calcul. Toujours est-il que voici à quoi ressemble l'intérieur d'un 80186. Elle contient :
* des registres d'adresse utilisés pour ses fonctionnalités de segmentation ;
* une unité de calcul d'adresse utilisée pour les calculs d'adresse ;
* une interface avec le bus mémoire, qu'on détaillera plus bas ;
* une mémoire FIFO où les instructions sont chargées en avance, appelée la ''Prefetch Input Queue''.
[[File:80186 arch.png|centre|vignette|upright=2.5|Microarchitecture du 80186.]]
Nous détaillerons la ''Prefetch Input Queue'' dans le chapitre sur le préchargement. Les registres et l'unité de calcul seront détaillés dans le chapitre sur la mémoire virtuelle. Il est intéressant de regarder l'interface avec le bus mémoire. Cette interface contient les registres d’interfaçage, ainsi que des circuits annexes.
Pour comprendre ce qu'elle fait, il faut savoir que le processeur communique avec la mémoire avec une séquence très précise, qui dure exactement quatre cycles. Les quatre cycles sont appelés des ''T-states'' et ils sont numérotés de 1 à 4 : T1, T2, T3 et T4.
Pour une lecture, l'adresse est envoyée lors du premier cycle, elle est disponible sur le bus au troisième cycle. Les cycles 1 et 3 sont inutilisés, le processeur se déconnecte du bus lors de ces cycles.
{|class="wikitable"
|-
! T1 !! T2 !! T3 !! T4
|-
| Adresse || || Donnée lue ||
|}
Pour une lecture, l'adresse est envoyée lors du premier cycle, la donnée est envoyée sur le bus lors des trois cycles suivants.
{|class="wikitable"
! T1 !! T2 !! T3 !! T4
|-
| Adresse || colspan="3 | Donnée à écrire
|}
Les ''timings'' vus plus haut ne sont effectivement observés que si la mémoire est assez rapide. Mais le 8086 pouvait être combiné avec des mémoires RAM assez diverses, certaines plus lentes que d'autres. Si la mémoire est trop lente, elle peut prendre plusieurs cycles avant de renvoyer la donnée lue ou d'accepter la donnée à écrire. Pour gérer la situation, le processeur pouvait attendre que la mémoire réponde, avec des ''wait states''.
Pour cela, la mémoire prévenait quand la donnée lue était disponible, ou quand une écriture était terminée. Le processeur avait une entrée READY qui indiquait que la mémoire était disponible ou qu'elle avait terminé son travail. Lors d'une lecture, la mémoire mettait cette entrée READY à 1 quand la donnée lue était disponible sur le bus mémoire. Idem pour une écriture. L'implémentation dans l'unité mémoire est très simple : le processeur restait dans le cycle T3 en attendant que l'entrée READY soit à 1. Lors d'une écriture, cela maintenait à donnée à écrire sur le bus mémoire. Pour une lecture, cela permettait de laisser le registre d’interfaçage des données connecté au bus mémoire, en attendant la donnée lue.
Nous venons de voir que le processeur pouvait rester dans l'état T3 si le besoin s'en fait sentir. Mais cette astuce est utilisée dans d'autres situations. Par exemple, le processeur dispose d'une instruction HALT, qui le déconnecte totalement du bus. Le processeur reste déconnecté tant qu'il ne reçoit pas un RESET ou une interruption. Il s'agit d'une forme limitée de mise en veille, qui n'utilisait pas de méthodes pour économiser de l'énergie. L'instruction HALT était implémentée en restant en permanence dans l'état T2. Le processeur attendait d'avoir terminé toute instruction précédente, puis lançait une pseudo-lecture et restait en état T1 en attendant une interruption ou un RESET.
Le registre d’interfaçage pour les adresses est connecté sur le bus mémoire lors du premier cycle, celui pour les données l'est à des cycles différents suivant l'opération demandée. La connexion/déconnexion des registres d’interfaçage se fait en utilisant des circuits trois-états. Il y a une couche de circuits trois-états entre le registre d’interfaçage et le bus mémoire, qui permet de connecter/déconnecter les registres du bus mémoire, mais aussi d'imposer le sens de transfert suivant qu'on fasse une lecture ou une écriture.
[[File:Circuit d'interfacage mémoire du 8086.png|centre|vignette|upright=2|Circuit d’interfaçage mémoire du 8086.]]
Le circuit en question est un circuit séquentiel assez simple, une petite "machine à état" comme on le dit en termes techniques. Le circuit séquentiel commande les circuits trois-états. Une implémentation naïve serait d'utiliser un compteur pouvant compter de 0 à 3, avec des comparateurs. Il faut, en tout :
* Un comparateur qui détermine si le compteur est au cycle 1. Si c'est le cas, il connecte le registre d’interfaçage d'adresse sur le bus mémoire.
* Un autre comparateur détermine si une lecture est en cours et que l'on est dans le troisième cycle. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode lecture.
* Un autre comparateur détermine si une écriture est en cours et que l'on est dans les cycles adéquats. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode écriture.
Afin de simplifier fortement le circuit, le compteur utilisé utilise la représentation ''one hot''. Pour rappel, cela veut dire qu'il y a une bascule pour chaque valeur que peut prendre le compteur. Le compteur ''one hot'' est composé de quatre bascules. La première bascule est à 1 pour le cycle T1, la seconde est à 1 pour le cycle T2, la troisième est à 1 pour le cycle T3, la quatrième l'est pour le cycle T4. Ainsi, on peut se passer de certains comparateurs. Par exemple, le comparateur pour l'adresse disparait : la bascule commande directement les circuits trois-états. Les deux autres comparateurs se transforment des circuits à respectivement une ou quatre portes logiques.
Incrémenter le compteur se fait cependant sous certaines conditions. La machine à état contient des circuits qui décident quand incrémenter le compteur. Pour cela, il y a un circuit qui détermine quand on peut passer du cycle T1 au cycle T2, un autre pour le passage de T2 à T3, etc. Les circuits en question sont un paquet de portes logiques assez simple. Ils reçoivent des signaux de commande provenant du séquenceur, ainsi que des entrées comme l'entrée READY pour les ''wait states''.
[[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|centre|vignette|upright=2|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]]
Maintenant, parlons des problématiques liées au calcul d'adresse. Tous les accès mémoire demandent de faire un calcul d'adresse, en raison de l'usage de la fonctionnalité de segmentation. Et ce calcul d'adresse prend deux cycles. L'adresse est calculée, puis que l'accès mémoire est réalisé. En tout, cela fait 6 cycles : 2 pour le calcul d'adresse, puis les cycles T1, T2, T3 et T4.
Une optimisation importante est que deux accès mémoire peuvent se recouvrir. A savoir que pendant qu'un accès mémoire a lieu sur le bus mémoire, on calcule l'adresse de l'accès du suivant. Les deux ont lieu dans des circuits séparés, ce qui fait que c'est possible. Pour donner un exemple, on peut calculer l'adresse de la prochaine instruction, pendant que l'instruction mémoire en cours est exécutée. Le séquenceur du processeur est configuré pour démarrer le calcul d'adresse au bon moment. Il attend que le compteur soit dans l'état T3 et T4.
{|class="wikitable"
|-
! Unité de calcul
| class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || || || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || || || ||
|-
! Bus mémoire
| || || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2
|}
: La machine à état est décrite en détail dans cet article de blog : [https://www.righto.com/2024/04/intel-8088-bus-state-machine.html Talking to memory: Inside the Intel 8088 processor's bus interface state machine ].
==Le chemin de données et son réseau d'interconnexions==
Nous venons de voir que le chemin de données contient une unité de calcul (parfois plusieurs), des registres isolés, un banc de registre, une unité mémoire. Le tout est chapeauté par une unité de contrôle qui commande le chemin de données, qui fera l'objet des prochains chapitres. Mais il faut maintenant relier registres, ALU et unité mémoire pour que l'ensemble fonctionne. Pour cela, diverses interconnexions internes au processeur se chargent de relier le tout.
Sur les anciens processeurs, les interconnexions sont assez simples et se résument à un ou deux '''bus internes au processeur''', reliés au bus mémoire. C'était la norme sur des architectures assez ancienne, qu'on n'a pas encore vu à ce point du cours, appelées les architectures à accumulateur et à pile. Mais ce n'est plus la solution utilisée actuellement. De nos jours, le réseaux d'interconnexion intra-processeur est un ensemble de connexions point à point entre ALU/registres/unité mémoire. Et paradoxalement, cela rend plus facile de comprendre ce réseau d'interconnexion.
===Introduction propédeutique : l'implémentation des modes d'adressage principaux===
L'organisation interne du processeur dépend fortement des modes d'adressage supportés. Pour simplifier les explications, nous allons séparer les modes d'adressage qui gèrent les pointeurs et les autres. Suivant que le processeur supporte les pointeurs ou non, l'organisation des bus interne est légèrement différente. La différence se voit sur les connexions avec le bus d'adresse et de données.
Tout processeur gère au minimum le '''mode d'adressage absolu''', où l'adresse est intégrée à l'instruction. Le séquenceur extrait l'adresse mémoire de l'instruction, et l'envoie sur le bus d'adresse. Pour cela, le séquenceur est relié au bus d'adresse, le chemin de donnée est relié au bus de données. Le chemin de donnée n'est pas connecté au bus d'adresse, il n'y a pas d'autres connexions.
[[File:Chemin de données sans support des pointeurs.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs]]
Le '''support des pointeurs''' demande d'intégrer des modes d'adressage dédiés : l'adressage indirect à registre, l'adresse base + indice, et les autres. Les pointeurs sont stockés dans le banc de registre et sont modifiés par l'unité de calcul. Pour supporter les pointeurs, le chemin de données est connecté sur le bus d'adresse avec le séquenceur. Suivant le mode d'adressage, le bus d'adresse est relié soit au chemin de données, soit au séquenceur.
[[File:Chemin de données avec support des pointeurs.png|centre|vignette|upright=2|Chemin de données avec support des pointeurs]]
Pour terminer, il faut parler des instructions de '''copie mémoire vers mémoire''', qui copient une donnée d'une adresse mémoire vers une autre. Elles ne se passent pas vraiment dans le chemin de données, mais se passent purement au niveau des registres d’interfaçage. L'usage d'un registre d’interfaçage unique permet d'implémenter ces instructions très facilement. Elle se fait en deux étapes : on copie la donnée dans le registre d’interfaçage, on l'écrit en mémoire RAM. L'adresse envoyée sur le bus d'adresse n'est pas la même lors des deux étapes.
===Le banc de registre est multi-port, pour gérer nativement les opérations dyadiques===
Les architectures RISC et CISC incorporent un banc de registre, qui est connecté aux unités de calcul et au bus mémoire. Et ce banc de registre peut être mono-port ou multiport. S'il a existé d'anciennes architectures utilisant un banc de registre mono-port, elles sont actuellement obsolètes. Nous les aborderons dans un chapitre dédié aux architectures dites canoniques, mais nous pouvons les laisser de côté pour le moment. De nos jours, tous les processeurs utilisent un banc de registre multi-port.
[[File:Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres).png|centre|vignette|upright=2|Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres)]]
Le banc de registre multiport est optimisé pour les opérations dyadiques. Il dispose précisément de deux ports de lecture et d'un port d'écriture pour l'écriture. Un port de lecture par opérande et le port d'écriture pour enregistrer le résultat. En clair, le processeur peut lire deux opérandes et écrire un résultat en un seul cycle d'horloge. L'avantage est que les opérations simples ne nécessitent qu'une micro-opération, pas plus.
[[File:ALU data paths.svg|centre|vignette|upright=1.5|Processeur LOAD-STORE avec un banc de registre multiport, avec les trois ports mis en évidence.]]
===Une architecture LOAD-STORE basique, avec adressage absolu===
Voyons maintenant comment l'implémentation d'une architecture RISC très simple, qui ne supporte pas les adressages pour les pointeurs, juste les adressages inhérent (à registres) et absolu (par adresse mémoire). Les instructions LOAD et STORE utilisent l'adressage absolu, géré par le séquenceur, reste à gérer l'échange entre banc de registres et bus de données. Une lecture LOAD relie le bus de données au port d'écriture du banc de registres, alors que l'écriture relie le bus au port de lecture du banc de registre. Pour cela, il faut ajouter des multiplexeurs sur les chemins existants, comme illustré par le schéma ci-dessous.
[[File:Bus interne au processeur sur archi LOAD STORE avec banc de registres multiport.png|centre|vignette|upright=2|Organisation interne d'une architecture LOAD STORE avec banc de registres multiport. Nous n'avons pas représenté les signaux de commandes envoyés par le séquenceur au chemin de données.]]
Ajoutons ensuite les instructions de copie entre registres, souvent appelées instruction COPY ou MOV. Elles existent sur la plupart des architectures LOAD-STORE. Une première solution boucle l'entrée du banc de registres sur son entrée, ce qui ne sert que pour les copies de registres.
[[File:Chemin de données d'une architecture LOAD-STORE.png|centre|vignette|upright=2|Chemin de données d'une architecture LOAD-STORE]]
Mais il existe une seconde solution, qui ne demande pas de modifier le chemin de données. Il est possible de faire passer les copies de données entre registres par l'ALU. Lors de ces copies, l'ALU une opération ''Pass through'', à savoir qu'elle recopie une des opérandes sur sa sortie. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, dans le sens où cela permet d'économiser des multiplexeurs. Mais nous verrons cela sous peu. D'ailleurs, dans la suite du chapitre, nous allons partir du principe que les copies entre registres passent par l'ALU, afin de simplifier les schémas.
===L'ajout des modes d'adressage indirects à registre pour les pointeurs===
Passons maintenant à l'implémentation des modes d'adressages pour les pointeurs. Avec eux, l'adresse mémoire à lire/écrire n'est pas intégrée dans une instruction, mais est soit dans un registre, soit calculée par l'ALU.
Le premier mode d'adressage de ce type est le mode d'adressage indirect à registre, où l'adresse à lire/écrire est dans un registre. L'implémenter demande donc de connecter la sortie du banc de registres au bus d'adresse. Il suffit d'ajouter un MUX en sortie d'un port de lecture.
[[File:Chemin de données à trois bus.png|centre|vignette|upright=2|Chemin de données à trois bus.]]
Le mode d'adressage base + indice est un mode d'adressage où l'adresse à lire/écrire est calculée à partir d'une adresse et d'un indice, tous deux présents dans un registre. Le calcul de l'adresse implique au minimum une addition et donc l'ALU. Dans ce cas, on doit connecter la sortie de l'unité de calcul au bus d'adresse.
[[File:Bus avec adressage base+index.png|centre|vignette|upright=2|Bus avec adressage base+index]]
Le chemin de données précédent gère aussi le mode d'adressage indirect avec pré-décrément. Pour rappel, ce mode d'adressage est une variante du mode d'adressage indirect, qui utilise une pointeur/adresse stocké dans un registre. La différence est que ce pointeur est décrémenté avant d'être envoyé sur le bus d'adresse. L'implémentation matérielle est la même que pour le mode Base + Indice : l'adresse est lue depuis les registres, décrémentée dans l'ALU, et envoyée sur le bus d'adresse.
Le schéma précédent montre que le bus d'adresse est connecté à un MUX avant l'ALU et un autre MUX après. Mais il est possible de se passer du premier MUX, utilisé pour le mode d'adressage indirect à registre. La condition est que l'ALU supporte l'opération ''pass through'', un NOP, qui recopie une opérande sur sa sortie. L'ALU fera une opération NOP pour le mode d'adressage indirect à registre, un calcul d'adresse pour le mode d'adressage base + indice. Par contre, faire ainsi rendra l'adressage indirect légèrement plus lent, vu que le temps de passage dans l'ALU sera compté.
[[File:Bus avec adressage indirect.png|centre|vignette|upright=2|Bus avec adressages pour les pointeurs, simplifié.]]
Dans ce qui va suivre, nous allons partir du principe que le processeur est implémenté en suivant le schéma précédent, afin d'avoir des schéma plus lisibles.
===L'adressage immédiat et les modes d'adressages exotiques===
Passons maintenant au mode d’adressage immédiat, qui permet de préciser une constante dans une instruction directement. La constante est extraite de l'instruction par le séquenceur, puis insérée au bon endroit dans le chemin de données. Pour les opérations arithmétiques/logiques/branchements, il faut insérer la constante extraite sur l'entrée de l'ALU. Sur certains processeurs, la constante peut être négative et doit alors subir une extension de signe dans un circuit spécialisé.
[[File:Chemin de données - Adressage immédiat avec extension de signe.png|centre|vignette|upright=2|Chemin de données - Adressage immédiat avec extension de signe.]]
L'implémentation précédente gère aussi les modes d'adressage base + décalage et absolu indexé. Pour rappel, le premier ajoute une constante à une adresse prise dans les registres, le second prend une adresse constante et lui ajoute un indice pris dans les registres. Dans les deux cas, on lit un registre, extrait une constante/adresse de l’instruction, additionne les deux dans l'ALU, avant d'envoyer le résultat sur le bus d'adresse. La seule difficulté est de désactiver l'extension de signe pour les adresses.
Le mode d'adressage absolu peut être traité de la même manière, si l'ALU est capable de faire des NOPs. L'adresse est insérée au même endroit que pour le mode d'adressage immédiat, parcours l'unité de calcul inchangée parce que NOP, et termine sur le bus d'adresse.
[[File:Chemin de données avec une ALU capable de faire des NOP.png|centre|vignette|upright=2|Chemin de données avec adressage immédiat étendu pour gérer des adresses.]]
Passons maintenant au cas particulier d'une instruction MOV qui copie une constante dans un registre. Il n'y a rien à faire si l'unité de calcul est capable d'effectuer une opération NOP/''pass through''. Pour charger une constante dans un registre, l'ALU est configurée pour faire un NOP, la constante traverse l'ALU et se retrouve dans les registres. Si l'ALU ne gère pas les NOP, la constante doit être envoyée sur l'entrée d'écriture du banc de registres, à travers un MUX dédié.
[[File:Implémentation de l'adressage immédiat dans le chemin de données.png|centre|vignette|upright=2|Implémentation de l'adressage immédiat dans le chemin de données]]
===Les architectures CISC : les opérations ''load-op''===
Tout ce qu'on a vu précédemment porte sur les processeurs de type LOAD-STORE, souvent confondus avec les processeurs de type RISC, où les accès mémoire sont séparés des instructions utilisant l'ALU. Il est maintenant temps de voir les processeurs CISC, qui gèrent des instructions ''load-op'', qui peuvent lire une opérande depuis la mémoire.
L'implémentation des opérations ''load-op'' relie le bus de donnée directement sur une entrée de l'unité de calcul, en utilisant encore une fois un multiplexeur. L'implémentation parait simple, mais c'est parce que toute la complexité est déportée dans le séquenceur. C'est lui qui se charge de détecter quand la lecture de l'opérande est terminée, quand l'opérande est disponible.
Les instructions ''load-op'' s'exécutent en plusieurs étapes, en plusieurs micro-opérations. Il y a typiquement une étape pour l'opérande à lire en mémoire et une étape de calcul. L'usage d'un registre d’interfaçage permet d'implémenter les instructions ''load-op'' très facilement. Une opération ''load-op'' charge l'opérande en mémoire dans un registre d’interfaçage, puis relier ce registre d’interfaçage sur une des entrées de l'ALU. Un simple multiplexeur suffit pour implémenter le tout, en plus des modifications adéquates du séquenceur.
[[File:Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire.png|centre|vignette|upright=2|Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire]]
Supporter les instructions multi-accès (qui font plusieurs accès mémoire) ne modifie pas fondamentalement le réseau d'interconnexion, ni le chemin de données La raison est que supporter les instructions multi-accès se fait au niveau du séquenceur. En réalité, les accès mémoire se font en série, l'un après l'autre, sous la commande du séquenceur qui émet plusieurs micro-opérations mémoire consécutives. Les données lues sont placées dans des registres d’interactivement mémoire, ce qui demande d'ajouter des registres d’interfaçage mémoire en plus.
==Annexe : le cas particulier du pointeur de pile==
Le pointeur de pile est un registre un peu particulier. Il peut être placé dans le chemin de données ou dans le séquenceur, voire dans l'unité de chargement, tout dépend du processeur. Tout dépend de si le pointeur de pile gère une pile d'adresses de retour ou une pile d'appel.
===Le pointeur de pile non-adressable explicitement===
Avec une pile d'adresse de retour, le pointeur de pile n'est pas adressable explicitement, il est juste adressé implicitement par des instructions d'appel de fonction CALL et des instructions de retour de fonction RET. Le pointeur de pile est alors juste incrémenté ou décrémenté par un pas constant, il ne subit pas d'autres opérations, son adressage est implicite. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Il n'y a pas besoin de le relier au chemin de données, vu qu'il n'échange pas de données avec les autres registres. Il y a alors plusieurs solutions, mais la plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Quelques processeurs simples disposent d'une pile d'appel très limitée, où le pointeur de pile n'est pas adressable explicitement. Il est adressé implicitement par les instruction CALL, RET, mais aussi PUSH et POP, mais aucune autre instruction ne permet cela. Là encore, le pointeur de pile ne communique pas avec les autres registres. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Là encore, le plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Dans les deux cas, le pointeur de pile est placé dans l'unité de contrôle, le séquenceur, et est associé à un incrémenteur dédié. Il se trouve que cet incrémenteur est souvent partagé avec le ''program counter''. En effet, les deux sont des adresses mémoire, qui sont incrémentées et décrémentées par pas constants, ne subissent pas d'autres opérations (si ce n'est des branchements, mais passons). Les ressemblances sont suffisantes pour fusionner les deux circuits. Ils peuvent donc avoir un '''incrémenteur partagé'''.
L'incrémenteur en question est donc partagé entre pointeur de pile, ''program counter'' et quelques autres registres similaires. Par exemple, le Z80 intégrait un registre pour le rafraichissement mémoire, qui était réalisé par le CPU à l'époque. Ce registre contenait la prochaine adresse mémoire à rafraichir, et était incrémenté à chaque rafraichissement d'une adresse. Et il était lui aussi intégré au séquenceur et incrémenté par l'incrémenteur partagé.
[[File:Organisation interne d'une architecture à pile.png|centre|vignette|upright=2|Organisation interne d'une architecture à pile]]
===Le pointeur de pile adressable explicitement===
Maintenant, étudions le cas d'une pile d'appel, précisément d'une pile d'appel avec des cadres de pile de taille variable. Sous ces conditions, le pointeur de pile est un registre adressable, avec un nom/numéro de registre dédié. Tel est par exemple le cas des processeurs x86 avec le registre ESP (''Extended Stack Pointer''). 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 calculs d'adresse, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres.
Dans ce cas, la meilleure solution est de placer le pointeur de pile dans le banc de registre généraux, avec les autres registres entiers. En faisant cela, la manipulation du pointeur de pile est faite par l'unité de calcul entière, pas besoin d'utiliser un incrémenteur dédiée. Il a existé des processeurs qui mettaient le pointeur de pile dans le banc de registre, mais l'incrémentaient avec un incrémenteur dédié, mais nous les verrons dans le chapitre sur les architectures à accumulateur. La raison est que sur les processeurs concernés, les adresses ne faisaient pas la même taille que les données : c'était des processeurs 8 bits, qui géraient des adresses de 16 bits.
==Annexe : l'implémentation du système d'''aliasing'' des registres des CPU x86==
Il y a quelques chapitres, nous avions parlé du système d'''aliasing'' des registres des CPU x86. Pour rappel, il permet de donner plusieurs noms de registre pour un même registre. Plus précisément, pour un registre 64 bits, le registre complet aura un nom de registre, les 32 bits de poids faible auront leur nom de registre dédié, idem pour les 16 bits de poids faible, etc. Il est possible de faire des calculs sur ces moitiés/quarts/huitièmes de registres sans problème.
===L'''aliasing'' du 8086, pour les registres 16 bits===
[[File:Register 8086.PNG|vignette|Register 8086]]
L'implémentation de l'''aliasing'' est apparue sur les premiers CPU Intel 16 bits, notamment le 8086. En tout, ils avaient quatre registres généraux 16 bits : AX, BX, CX et DX. Ces quatre registres 16 bits étaient coupés en deux octets, chacun adressable. Par exemple, le registre AX était coupé en deux octets nommés AH et AL, chacun ayant son propre nom/numéro de registre. Les instructions d'addition/soustraction pouvaient manipuler le registre AL, ou le registre AH, ce qui modifiait les 8 bits de poids faible ou fort selon le registre choisit.
Le banc de registre ne gére que 4 registres de 16 bits, à savoir AX, BX, CX et DX. Lors d'une lecture d'un registre 8 bits, le registre 16 bit entier est lu depuis le banc de registre, mais les bits inutiles sont ignorés. Par contre, l'écriture peut se faire soit avec 16 bits d'un coup, soit pour seulement un octet. Le port d'écriture du banc de registre peut être configuré de manière à autoriser l'écriture soit sur les 16 bits du registre, soit seulement sur les 8 bits de poids faible, soit écrire dans les 8 bits de poids fort.
[[File:Port d'écriture du banc de registre du 8086.png|centre|vignette|upright=2.5|Port d'écriture du banc de registre du 8086]]
Une opération sur un registre 8 bits se passe comme suit. Premièrement, on lit le registre 16 bits complet depuis le banc de registre. Si l'on a sélectionné l'octet de poids faible, il ne se passe rien de particulier, l'opérande 16 bits est envoyée directement à l'ALU. Mais si on a sélectionné l'octet de poids fort, la valeur lue est décalée de 7 rangs pour atterrir dans les 8 octets de poids faible. Ensuite, l'unité de calcul fait un calcul avec cet opérande, un calcul 16 bits tout ce qu'il y a de plus classique. Troisièmement, le résultat est enregistré dans le banc de registre, en le configurant convenablement. La configuration précise s'il faut enregistrer le résultat dans un registre 16 bits, soit seulement dans l'octet de poids faible/fort.
Afin de simplifier le câblage, les 16 bits des registres AX/BX/CX/DX sont entrelacés d'une manière un peu particulière. Intuitivement, on s'attend à ce que les bits soient physiquement dans le même ordre que dans le registre : le bit 0 est placé à côté du bit 1, suivi par le bit 2, etc. Mais à la place, l'octet de poids fort et de poids faible sont mélangés. Deux bits consécutifs appartiennent à deux octets différents. Le tout est décrit dans le tableau ci-dessous.
{|class="wikitable"
|-
! Registre 16 bits normal
| class="f_bleu" | 15
| class="f_bleu" | 14
| class="f_bleu" | 13
| class="f_bleu" | 12
| class="f_bleu" | 11
| class="f_bleu" | 10
| class="f_bleu" | 9
| class="f_bleu" | 8
| class="f_rouge" | 7
| class="f_rouge" | 6
| class="f_rouge" | 5
| class="f_rouge" | 4
| class="f_rouge" | 3
| class="f_rouge" | 2
| class="f_rouge" | 1
| class="f_rouge" | 0
|-
! Registre 16 bits du 8086
| class="f_bleu" | 15
| class="f_rouge" | 7
| class="f_bleu" | 14
| class="f_rouge" | 6
| class="f_bleu" | 13
| class="f_rouge" | 5
| class="f_bleu" | 12
| class="f_rouge" | 4
| class="f_bleu" | 11
| class="f_rouge" | 3
| class="f_bleu" | 10
| class="f_rouge" | 2
| class="f_bleu" | 9
| class="f_rouge" | 1
| class="f_bleu" | 8
| class="f_rouge" | 0
|}
En faisant cela, le décaleur en entrée de l'ALU est bien plus simple. Il y a 8 multiplexeurs, mais le câblage est bien plus simple. Par contre, en sortie de l'ALU, il faut remettre les bits du résultat dans l'ordre adéquat, celui du registre 8086. Pour cela, les interconnexions sur le port d'écriture sont conçues pour. Il faut juste mettre les fils de sortie de l'ALU sur la bonne entrée, par besoin de multiplexeurs.
===L'''aliasing'' sur les processeurs x86 32/64 bits===
Les processeurs x86 32 et 64 bits ont un système d'''aliasing'' qui complète le système précédent. Les processeurs 32 bits étendent les registres 16 bits existants à 32 bits. Pour ce faire, le registre 32 bit a un nouveau nom de registre, distincts du nom de registre utilisé pour l'ancien registre 16 bits. Il est possible d'adresser les 16 bits de poids faible de ce registre, avec le même nom de registre que celui utilisé pour le registre 16 sur les processeurs d'avant. Même chose avec les processeurs 64, avec l'ajout d'un nouveau nom de registre pour adresser un registre de 64 bit complet.
En soit, implémenter ce système n'est pas compliqué. Prenons le cas du registre RAX (64 bits), et de ses subdivisions nommées EAX (32 bits), AX (16 bits). À l'intérieur du banc de registre, il n'y a que le registre RAX. Le banc de registre ne comprend qu'un seul nom de registre : RAX. Les subdivisions EAX et AX n'existent qu'au niveau de l'écriture dans le banc de registre. L'écriture dans le banc de registre est configurable, de manière à ne modifier que les bits adéquats. Le résultat d'un calcul de l'ALU fait 64 bits, il est envoyé sur le port d'écriture. À ce niveau, soit les 64 bits sont écrits dans le registre, soit seulement les 32/16 bits de poids faible. Le système du 8086 est préservé pour les écritures dans les 16 bits de poids faible.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les composants d'un processeur
| prevText=Les composants d'un processeur
| next=L'unité de chargement et le program counter
| nextText=L'unité de chargement et le program counter
}}
</noinclude>
jf4a6kmltoaje126vunc6ur8cu0rf6y
772058
772057
2026-09-11T23:20:47Z
Mewtow
31375
/* L'unité de calcul d'adresse */
772058
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
: Rappelons que dans ce chapitre, ainsi que dans les quatre chapitres suivants, nous ne parlons que des architectures à registres généraux. Aussi, nous mettons de côté les anciens processeurs à accumulateur et les CPU 8 bits, commercialisés avant l'arrivée des registres généraux, ainsi que certaines architectures non-conventionnelles comme les DSPs, qui ont des unités de calcul d'adresse dédiées. Mais nous ne parlerons pas de celles-ci dans ce chapitre.
L'AGU est utilisée pour certains modes d'adressage, à savoir ceux qui demandent de combiner une adresse avec soit un indice, soit un décalage, plus rarement les deux. Les calculs d'adresse prennent une adresse de base, et lui ajoutent un indice ou un décalage. Il faut parfois décaler l'indice, de quelques rangs. Pas besoin de multiplications, de divisions, ou d'autres opération plus complexe : décalages et additions/soustractions suffisent. L'AGU est donc beaucoup plus simple qu'une ALU normale et se résume à un additionneur-soustracteur couplé à un décaleur. Son cout en circuits est ridicule.
Mais pourquoi ajouter une AGU, alors que l'ALU entière suffit largement pour implémenter les modes d'adressage complexes. Il est parfaitement possible de faire les calculs d'adresse dans l'ALU entière, puis d'envoyer le résultat à l'unité mémoire. Sauf que faire ainsi pose plusieurs problèmes. Premièrement, cela complexifie le travail du séquenceur, qui doit enchainer deux micro-opérations. Ensuite, cela demande de câbler un chemin reliant la sortie de l'ALU entière à l'unité mémoire. Rien d'impossible, on verra même cela en détail à la fin du chapitre. Mais cette connexion est souvent difficile à câbler.
En ajoutant une AGU, les deux défauts disparaissent. La liaison entre ALU et unité mémoire disparait, l'unité mémoire n'est reliée qu'aux registres. Le séquenceur est aussi simplifié, vu que les accès mémoire ne font plus qu'une seule micro-opération, il n'y a plus besoin d'utiliser une micro-opération pour les calculs d'adresse. Le câblage du processeur est simplifié, au prix d'un cout modéré en transistors. On peut même obtenir nune économie, les simplifications du séquenceur sur-compensant le cout en transistors de l'AGU.
Parlons rapidement de l'implémentation du mode d'adressage "Base + Indice + Décalage", qui additionne une adresse de base, un indice (mis à l'échelle) et un décalage (une constante immédiate). Il demande d'additionner trois opérandes, chose que l'ALU entière ne sait pas faire (sauf exceptions). En théorie, une AGU peut réaliser ce calcul sans cout, en utilisant un additionneur ''carry save'', capable d'additionner trois opérandes à la fois. L'AGU est alors implémentée avec un circuit décaleur, pour calibrer l'indice, et cet additionneur trois-opérandes. Le résultat est que l'on peut faire les calculs d'adresse en un seul cycle d'horloge, pour un cout en hardware modéré. Par contre, il faut relier l'unité de calcul au séquenceur, mais aussi aux registres.
[[File:Unité de calcul d'adresse.png|centre|vignette|upright=2|Unité de calcul d'adresse]]
Chose étrange, ce n'est pas ce qui était fait sur les premiers processeurs Intel. Sur ces CPUs, le calcul de l'adresse se faisait en deux cycles d'horloge. Lors du premier cycle d'horloge, l'ALU entière faisait le calcul d'adresse "Base + Indice". Lors du second cycle, le résultat de l'addition précédente est additionné avec le décalage, dans l'AGU. Pour cela, l'AGU était reliée à l'ALU entière et à l'unité de contrôle. Difficile de comprendre cette décision, l'implémentation ne profitant que partiellement des avantages mentionnés plus haut pour ce mode d'adressage.
===Le rafraichissement mémoire optimisé et le contrôleur mémoire intégré===
Depuis les années 80, les processeurs sont souvent combinés avec une mémoire principale de type DRAM. De telles mémoires doivent être rafraichies régulièrement pour ne pas perdre de données. Le rafraichissement se fait généralement adresse par adresse, ou ligne par ligne (les lignes sont des super-bytes internes à la DRAM). Le rafraichissement est en théorie géré par le contrôleur mémoire installé sur la carte mère. Mais au tout début de l'informatique, du temps des processeurs 8 bits, le rafraichissement mémoire était géré directement par le processeur.
Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le processeur 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''. Et tout cela était intégré à l'unité d'accès mémoire.
Depuis les années 2000, les processeurs modernes ont un contrôleur mémoire DRAM intégré directement dans le processeur. Ce qui fait qu'ils gèrent non seulement le rafraichissement, mais aussi d'autres fonctions bien pus complexes.
===L'interface de l'unité mémoire===
Vu de l'extérieur, l'unité mémoire ressemble à n'importe quel circuit électronique, avec des entrées et des sorties. L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie. Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. Si l'unité mémoire incorpore une AGU, on envoie aussi les indices et autres données sur le port d'entrée. Le port de sortie est utilisé pour récupérer le résultat des lectures. Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues.
L'unité mémoire est connectée au reste du processeur grâce à un réseau d'interconnexion qu'on étudiera plus loin. Les connexions principales sont celles avec les registres : les adresses à lire/écrire sont souvent lues depuis les registres, les lectures copient une donnée dans un registre. Il peut y avoir une connexion avec l'unité de calcul pour les opérations ''load-up'', ou pour le calcul d'adresse, mais c'est secondaire.
[[File:Unité d'accès mémoire LOAD-STORE.png|centre|vignette|upright=2|Unité d'accès mémoire LOAD-STORE.]]
===Un exemple : l'unité mémoire du 8086 et ses dérivés===
Après avoir vu la théorie, voyons un exemple du monde réel. Nous allons étudier l'exemple du processeur Intel 8086 et de son dérivé, le 80186. Ces processeurs avaient une particularité : leur unité mémoire était fortement séparée du reste du processeur. Le processeur était séparé en une ''Bus Interface Unit'' et une ''Execution Unit''. L'''Execution Unit'' a un nom trompeur : c'est l'ensemble du processeur, unité mémoire exclue. La ''Bus Interface Unit'' est l'unité mémoire. Elle se charge à la fois du chargement des données et des instructions.
Cependant, nous n'allons pouvoir détailler cette unité dans le détail, car nous n'avons pas encore vu la mémoire virtuelle, ni le préchargement. Mais cela ne nous gênera pas, vous aurez simplement quelques détails passés sous silence pour ce qui est de l'unité de calcul. Toujours est-il que voici à quoi ressemble l'intérieur d'un 80186. Elle contient :
* des registres d'adresse utilisés pour ses fonctionnalités de segmentation ;
* une unité de calcul d'adresse utilisée pour les calculs d'adresse ;
* une interface avec le bus mémoire, qu'on détaillera plus bas ;
* une mémoire FIFO où les instructions sont chargées en avance, appelée la ''Prefetch Input Queue''.
[[File:80186 arch.png|centre|vignette|upright=2.5|Microarchitecture du 80186.]]
Nous détaillerons la ''Prefetch Input Queue'' dans le chapitre sur le préchargement. Les registres et l'unité de calcul seront détaillés dans le chapitre sur la mémoire virtuelle. Il est intéressant de regarder l'interface avec le bus mémoire. Cette interface contient les registres d’interfaçage, ainsi que des circuits annexes.
Pour comprendre ce qu'elle fait, il faut savoir que le processeur communique avec la mémoire avec une séquence très précise, qui dure exactement quatre cycles. Les quatre cycles sont appelés des ''T-states'' et ils sont numérotés de 1 à 4 : T1, T2, T3 et T4.
Pour une lecture, l'adresse est envoyée lors du premier cycle, elle est disponible sur le bus au troisième cycle. Les cycles 1 et 3 sont inutilisés, le processeur se déconnecte du bus lors de ces cycles.
{|class="wikitable"
|-
! T1 !! T2 !! T3 !! T4
|-
| Adresse || || Donnée lue ||
|}
Pour une lecture, l'adresse est envoyée lors du premier cycle, la donnée est envoyée sur le bus lors des trois cycles suivants.
{|class="wikitable"
! T1 !! T2 !! T3 !! T4
|-
| Adresse || colspan="3 | Donnée à écrire
|}
Les ''timings'' vus plus haut ne sont effectivement observés que si la mémoire est assez rapide. Mais le 8086 pouvait être combiné avec des mémoires RAM assez diverses, certaines plus lentes que d'autres. Si la mémoire est trop lente, elle peut prendre plusieurs cycles avant de renvoyer la donnée lue ou d'accepter la donnée à écrire. Pour gérer la situation, le processeur pouvait attendre que la mémoire réponde, avec des ''wait states''.
Pour cela, la mémoire prévenait quand la donnée lue était disponible, ou quand une écriture était terminée. Le processeur avait une entrée READY qui indiquait que la mémoire était disponible ou qu'elle avait terminé son travail. Lors d'une lecture, la mémoire mettait cette entrée READY à 1 quand la donnée lue était disponible sur le bus mémoire. Idem pour une écriture. L'implémentation dans l'unité mémoire est très simple : le processeur restait dans le cycle T3 en attendant que l'entrée READY soit à 1. Lors d'une écriture, cela maintenait à donnée à écrire sur le bus mémoire. Pour une lecture, cela permettait de laisser le registre d’interfaçage des données connecté au bus mémoire, en attendant la donnée lue.
Nous venons de voir que le processeur pouvait rester dans l'état T3 si le besoin s'en fait sentir. Mais cette astuce est utilisée dans d'autres situations. Par exemple, le processeur dispose d'une instruction HALT, qui le déconnecte totalement du bus. Le processeur reste déconnecté tant qu'il ne reçoit pas un RESET ou une interruption. Il s'agit d'une forme limitée de mise en veille, qui n'utilisait pas de méthodes pour économiser de l'énergie. L'instruction HALT était implémentée en restant en permanence dans l'état T2. Le processeur attendait d'avoir terminé toute instruction précédente, puis lançait une pseudo-lecture et restait en état T1 en attendant une interruption ou un RESET.
Le registre d’interfaçage pour les adresses est connecté sur le bus mémoire lors du premier cycle, celui pour les données l'est à des cycles différents suivant l'opération demandée. La connexion/déconnexion des registres d’interfaçage se fait en utilisant des circuits trois-états. Il y a une couche de circuits trois-états entre le registre d’interfaçage et le bus mémoire, qui permet de connecter/déconnecter les registres du bus mémoire, mais aussi d'imposer le sens de transfert suivant qu'on fasse une lecture ou une écriture.
[[File:Circuit d'interfacage mémoire du 8086.png|centre|vignette|upright=2|Circuit d’interfaçage mémoire du 8086.]]
Le circuit en question est un circuit séquentiel assez simple, une petite "machine à état" comme on le dit en termes techniques. Le circuit séquentiel commande les circuits trois-états. Une implémentation naïve serait d'utiliser un compteur pouvant compter de 0 à 3, avec des comparateurs. Il faut, en tout :
* Un comparateur qui détermine si le compteur est au cycle 1. Si c'est le cas, il connecte le registre d’interfaçage d'adresse sur le bus mémoire.
* Un autre comparateur détermine si une lecture est en cours et que l'on est dans le troisième cycle. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode lecture.
* Un autre comparateur détermine si une écriture est en cours et que l'on est dans les cycles adéquats. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode écriture.
Afin de simplifier fortement le circuit, le compteur utilisé utilise la représentation ''one hot''. Pour rappel, cela veut dire qu'il y a une bascule pour chaque valeur que peut prendre le compteur. Le compteur ''one hot'' est composé de quatre bascules. La première bascule est à 1 pour le cycle T1, la seconde est à 1 pour le cycle T2, la troisième est à 1 pour le cycle T3, la quatrième l'est pour le cycle T4. Ainsi, on peut se passer de certains comparateurs. Par exemple, le comparateur pour l'adresse disparait : la bascule commande directement les circuits trois-états. Les deux autres comparateurs se transforment des circuits à respectivement une ou quatre portes logiques.
Incrémenter le compteur se fait cependant sous certaines conditions. La machine à état contient des circuits qui décident quand incrémenter le compteur. Pour cela, il y a un circuit qui détermine quand on peut passer du cycle T1 au cycle T2, un autre pour le passage de T2 à T3, etc. Les circuits en question sont un paquet de portes logiques assez simple. Ils reçoivent des signaux de commande provenant du séquenceur, ainsi que des entrées comme l'entrée READY pour les ''wait states''.
[[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|centre|vignette|upright=2|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]]
Maintenant, parlons des problématiques liées au calcul d'adresse. Tous les accès mémoire demandent de faire un calcul d'adresse, en raison de l'usage de la fonctionnalité de segmentation. Et ce calcul d'adresse prend deux cycles. L'adresse est calculée, puis que l'accès mémoire est réalisé. En tout, cela fait 6 cycles : 2 pour le calcul d'adresse, puis les cycles T1, T2, T3 et T4.
Une optimisation importante est que deux accès mémoire peuvent se recouvrir. A savoir que pendant qu'un accès mémoire a lieu sur le bus mémoire, on calcule l'adresse de l'accès du suivant. Les deux ont lieu dans des circuits séparés, ce qui fait que c'est possible. Pour donner un exemple, on peut calculer l'adresse de la prochaine instruction, pendant que l'instruction mémoire en cours est exécutée. Le séquenceur du processeur est configuré pour démarrer le calcul d'adresse au bon moment. Il attend que le compteur soit dans l'état T3 et T4.
{|class="wikitable"
|-
! Unité de calcul
| class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || || || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || || || ||
|-
! Bus mémoire
| || || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2
|}
: La machine à état est décrite en détail dans cet article de blog : [https://www.righto.com/2024/04/intel-8088-bus-state-machine.html Talking to memory: Inside the Intel 8088 processor's bus interface state machine ].
==Le chemin de données et son réseau d'interconnexions==
Nous venons de voir que le chemin de données contient une unité de calcul (parfois plusieurs), des registres isolés, un banc de registre, une unité mémoire. Le tout est chapeauté par une unité de contrôle qui commande le chemin de données, qui fera l'objet des prochains chapitres. Mais il faut maintenant relier registres, ALU et unité mémoire pour que l'ensemble fonctionne. Pour cela, diverses interconnexions internes au processeur se chargent de relier le tout.
Sur les anciens processeurs, les interconnexions sont assez simples et se résument à un ou deux '''bus internes au processeur''', reliés au bus mémoire. C'était la norme sur des architectures assez ancienne, qu'on n'a pas encore vu à ce point du cours, appelées les architectures à accumulateur et à pile. Mais ce n'est plus la solution utilisée actuellement. De nos jours, le réseaux d'interconnexion intra-processeur est un ensemble de connexions point à point entre ALU/registres/unité mémoire. Et paradoxalement, cela rend plus facile de comprendre ce réseau d'interconnexion.
===Introduction propédeutique : l'implémentation des modes d'adressage principaux===
L'organisation interne du processeur dépend fortement des modes d'adressage supportés. Pour simplifier les explications, nous allons séparer les modes d'adressage qui gèrent les pointeurs et les autres. Suivant que le processeur supporte les pointeurs ou non, l'organisation des bus interne est légèrement différente. La différence se voit sur les connexions avec le bus d'adresse et de données.
Tout processeur gère au minimum le '''mode d'adressage absolu''', où l'adresse est intégrée à l'instruction. Le séquenceur extrait l'adresse mémoire de l'instruction, et l'envoie sur le bus d'adresse. Pour cela, le séquenceur est relié au bus d'adresse, le chemin de donnée est relié au bus de données. Le chemin de donnée n'est pas connecté au bus d'adresse, il n'y a pas d'autres connexions.
[[File:Chemin de données sans support des pointeurs.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs]]
Le '''support des pointeurs''' demande d'intégrer des modes d'adressage dédiés : l'adressage indirect à registre, l'adresse base + indice, et les autres. Les pointeurs sont stockés dans le banc de registre et sont modifiés par l'unité de calcul. Pour supporter les pointeurs, le chemin de données est connecté sur le bus d'adresse avec le séquenceur. Suivant le mode d'adressage, le bus d'adresse est relié soit au chemin de données, soit au séquenceur.
[[File:Chemin de données avec support des pointeurs.png|centre|vignette|upright=2|Chemin de données avec support des pointeurs]]
Pour terminer, il faut parler des instructions de '''copie mémoire vers mémoire''', qui copient une donnée d'une adresse mémoire vers une autre. Elles ne se passent pas vraiment dans le chemin de données, mais se passent purement au niveau des registres d’interfaçage. L'usage d'un registre d’interfaçage unique permet d'implémenter ces instructions très facilement. Elle se fait en deux étapes : on copie la donnée dans le registre d’interfaçage, on l'écrit en mémoire RAM. L'adresse envoyée sur le bus d'adresse n'est pas la même lors des deux étapes.
===Le banc de registre est multi-port, pour gérer nativement les opérations dyadiques===
Les architectures RISC et CISC incorporent un banc de registre, qui est connecté aux unités de calcul et au bus mémoire. Et ce banc de registre peut être mono-port ou multiport. S'il a existé d'anciennes architectures utilisant un banc de registre mono-port, elles sont actuellement obsolètes. Nous les aborderons dans un chapitre dédié aux architectures dites canoniques, mais nous pouvons les laisser de côté pour le moment. De nos jours, tous les processeurs utilisent un banc de registre multi-port.
[[File:Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres).png|centre|vignette|upright=2|Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres)]]
Le banc de registre multiport est optimisé pour les opérations dyadiques. Il dispose précisément de deux ports de lecture et d'un port d'écriture pour l'écriture. Un port de lecture par opérande et le port d'écriture pour enregistrer le résultat. En clair, le processeur peut lire deux opérandes et écrire un résultat en un seul cycle d'horloge. L'avantage est que les opérations simples ne nécessitent qu'une micro-opération, pas plus.
[[File:ALU data paths.svg|centre|vignette|upright=1.5|Processeur LOAD-STORE avec un banc de registre multiport, avec les trois ports mis en évidence.]]
===Une architecture LOAD-STORE basique, avec adressage absolu===
Voyons maintenant comment l'implémentation d'une architecture RISC très simple, qui ne supporte pas les adressages pour les pointeurs, juste les adressages inhérent (à registres) et absolu (par adresse mémoire). Les instructions LOAD et STORE utilisent l'adressage absolu, géré par le séquenceur, reste à gérer l'échange entre banc de registres et bus de données. Une lecture LOAD relie le bus de données au port d'écriture du banc de registres, alors que l'écriture relie le bus au port de lecture du banc de registre. Pour cela, il faut ajouter des multiplexeurs sur les chemins existants, comme illustré par le schéma ci-dessous.
[[File:Bus interne au processeur sur archi LOAD STORE avec banc de registres multiport.png|centre|vignette|upright=2|Organisation interne d'une architecture LOAD STORE avec banc de registres multiport. Nous n'avons pas représenté les signaux de commandes envoyés par le séquenceur au chemin de données.]]
Ajoutons ensuite les instructions de copie entre registres, souvent appelées instruction COPY ou MOV. Elles existent sur la plupart des architectures LOAD-STORE. Une première solution boucle l'entrée du banc de registres sur son entrée, ce qui ne sert que pour les copies de registres.
[[File:Chemin de données d'une architecture LOAD-STORE.png|centre|vignette|upright=2|Chemin de données d'une architecture LOAD-STORE]]
Mais il existe une seconde solution, qui ne demande pas de modifier le chemin de données. Il est possible de faire passer les copies de données entre registres par l'ALU. Lors de ces copies, l'ALU une opération ''Pass through'', à savoir qu'elle recopie une des opérandes sur sa sortie. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, dans le sens où cela permet d'économiser des multiplexeurs. Mais nous verrons cela sous peu. D'ailleurs, dans la suite du chapitre, nous allons partir du principe que les copies entre registres passent par l'ALU, afin de simplifier les schémas.
===L'ajout des modes d'adressage indirects à registre pour les pointeurs===
Passons maintenant à l'implémentation des modes d'adressages pour les pointeurs. Avec eux, l'adresse mémoire à lire/écrire n'est pas intégrée dans une instruction, mais est soit dans un registre, soit calculée par l'ALU.
Le premier mode d'adressage de ce type est le mode d'adressage indirect à registre, où l'adresse à lire/écrire est dans un registre. L'implémenter demande donc de connecter la sortie du banc de registres au bus d'adresse. Il suffit d'ajouter un MUX en sortie d'un port de lecture.
[[File:Chemin de données à trois bus.png|centre|vignette|upright=2|Chemin de données à trois bus.]]
Le mode d'adressage base + indice est un mode d'adressage où l'adresse à lire/écrire est calculée à partir d'une adresse et d'un indice, tous deux présents dans un registre. Le calcul de l'adresse implique au minimum une addition et donc l'ALU. Dans ce cas, on doit connecter la sortie de l'unité de calcul au bus d'adresse.
[[File:Bus avec adressage base+index.png|centre|vignette|upright=2|Bus avec adressage base+index]]
Le chemin de données précédent gère aussi le mode d'adressage indirect avec pré-décrément. Pour rappel, ce mode d'adressage est une variante du mode d'adressage indirect, qui utilise une pointeur/adresse stocké dans un registre. La différence est que ce pointeur est décrémenté avant d'être envoyé sur le bus d'adresse. L'implémentation matérielle est la même que pour le mode Base + Indice : l'adresse est lue depuis les registres, décrémentée dans l'ALU, et envoyée sur le bus d'adresse.
Le schéma précédent montre que le bus d'adresse est connecté à un MUX avant l'ALU et un autre MUX après. Mais il est possible de se passer du premier MUX, utilisé pour le mode d'adressage indirect à registre. La condition est que l'ALU supporte l'opération ''pass through'', un NOP, qui recopie une opérande sur sa sortie. L'ALU fera une opération NOP pour le mode d'adressage indirect à registre, un calcul d'adresse pour le mode d'adressage base + indice. Par contre, faire ainsi rendra l'adressage indirect légèrement plus lent, vu que le temps de passage dans l'ALU sera compté.
[[File:Bus avec adressage indirect.png|centre|vignette|upright=2|Bus avec adressages pour les pointeurs, simplifié.]]
Dans ce qui va suivre, nous allons partir du principe que le processeur est implémenté en suivant le schéma précédent, afin d'avoir des schéma plus lisibles.
===L'adressage immédiat et les modes d'adressages exotiques===
Passons maintenant au mode d’adressage immédiat, qui permet de préciser une constante dans une instruction directement. La constante est extraite de l'instruction par le séquenceur, puis insérée au bon endroit dans le chemin de données. Pour les opérations arithmétiques/logiques/branchements, il faut insérer la constante extraite sur l'entrée de l'ALU. Sur certains processeurs, la constante peut être négative et doit alors subir une extension de signe dans un circuit spécialisé.
[[File:Chemin de données - Adressage immédiat avec extension de signe.png|centre|vignette|upright=2|Chemin de données - Adressage immédiat avec extension de signe.]]
L'implémentation précédente gère aussi les modes d'adressage base + décalage et absolu indexé. Pour rappel, le premier ajoute une constante à une adresse prise dans les registres, le second prend une adresse constante et lui ajoute un indice pris dans les registres. Dans les deux cas, on lit un registre, extrait une constante/adresse de l’instruction, additionne les deux dans l'ALU, avant d'envoyer le résultat sur le bus d'adresse. La seule difficulté est de désactiver l'extension de signe pour les adresses.
Le mode d'adressage absolu peut être traité de la même manière, si l'ALU est capable de faire des NOPs. L'adresse est insérée au même endroit que pour le mode d'adressage immédiat, parcours l'unité de calcul inchangée parce que NOP, et termine sur le bus d'adresse.
[[File:Chemin de données avec une ALU capable de faire des NOP.png|centre|vignette|upright=2|Chemin de données avec adressage immédiat étendu pour gérer des adresses.]]
Passons maintenant au cas particulier d'une instruction MOV qui copie une constante dans un registre. Il n'y a rien à faire si l'unité de calcul est capable d'effectuer une opération NOP/''pass through''. Pour charger une constante dans un registre, l'ALU est configurée pour faire un NOP, la constante traverse l'ALU et se retrouve dans les registres. Si l'ALU ne gère pas les NOP, la constante doit être envoyée sur l'entrée d'écriture du banc de registres, à travers un MUX dédié.
[[File:Implémentation de l'adressage immédiat dans le chemin de données.png|centre|vignette|upright=2|Implémentation de l'adressage immédiat dans le chemin de données]]
===Les architectures CISC : les opérations ''load-op''===
Tout ce qu'on a vu précédemment porte sur les processeurs de type LOAD-STORE, souvent confondus avec les processeurs de type RISC, où les accès mémoire sont séparés des instructions utilisant l'ALU. Il est maintenant temps de voir les processeurs CISC, qui gèrent des instructions ''load-op'', qui peuvent lire une opérande depuis la mémoire.
L'implémentation des opérations ''load-op'' relie le bus de donnée directement sur une entrée de l'unité de calcul, en utilisant encore une fois un multiplexeur. L'implémentation parait simple, mais c'est parce que toute la complexité est déportée dans le séquenceur. C'est lui qui se charge de détecter quand la lecture de l'opérande est terminée, quand l'opérande est disponible.
Les instructions ''load-op'' s'exécutent en plusieurs étapes, en plusieurs micro-opérations. Il y a typiquement une étape pour l'opérande à lire en mémoire et une étape de calcul. L'usage d'un registre d’interfaçage permet d'implémenter les instructions ''load-op'' très facilement. Une opération ''load-op'' charge l'opérande en mémoire dans un registre d’interfaçage, puis relier ce registre d’interfaçage sur une des entrées de l'ALU. Un simple multiplexeur suffit pour implémenter le tout, en plus des modifications adéquates du séquenceur.
[[File:Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire.png|centre|vignette|upright=2|Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire]]
Supporter les instructions multi-accès (qui font plusieurs accès mémoire) ne modifie pas fondamentalement le réseau d'interconnexion, ni le chemin de données La raison est que supporter les instructions multi-accès se fait au niveau du séquenceur. En réalité, les accès mémoire se font en série, l'un après l'autre, sous la commande du séquenceur qui émet plusieurs micro-opérations mémoire consécutives. Les données lues sont placées dans des registres d’interactivement mémoire, ce qui demande d'ajouter des registres d’interfaçage mémoire en plus.
==Annexe : le cas particulier du pointeur de pile==
Le pointeur de pile est un registre un peu particulier. Il peut être placé dans le chemin de données ou dans le séquenceur, voire dans l'unité de chargement, tout dépend du processeur. Tout dépend de si le pointeur de pile gère une pile d'adresses de retour ou une pile d'appel.
===Le pointeur de pile non-adressable explicitement===
Avec une pile d'adresse de retour, le pointeur de pile n'est pas adressable explicitement, il est juste adressé implicitement par des instructions d'appel de fonction CALL et des instructions de retour de fonction RET. Le pointeur de pile est alors juste incrémenté ou décrémenté par un pas constant, il ne subit pas d'autres opérations, son adressage est implicite. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Il n'y a pas besoin de le relier au chemin de données, vu qu'il n'échange pas de données avec les autres registres. Il y a alors plusieurs solutions, mais la plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Quelques processeurs simples disposent d'une pile d'appel très limitée, où le pointeur de pile n'est pas adressable explicitement. Il est adressé implicitement par les instruction CALL, RET, mais aussi PUSH et POP, mais aucune autre instruction ne permet cela. Là encore, le pointeur de pile ne communique pas avec les autres registres. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Là encore, le plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Dans les deux cas, le pointeur de pile est placé dans l'unité de contrôle, le séquenceur, et est associé à un incrémenteur dédié. Il se trouve que cet incrémenteur est souvent partagé avec le ''program counter''. En effet, les deux sont des adresses mémoire, qui sont incrémentées et décrémentées par pas constants, ne subissent pas d'autres opérations (si ce n'est des branchements, mais passons). Les ressemblances sont suffisantes pour fusionner les deux circuits. Ils peuvent donc avoir un '''incrémenteur partagé'''.
L'incrémenteur en question est donc partagé entre pointeur de pile, ''program counter'' et quelques autres registres similaires. Par exemple, le Z80 intégrait un registre pour le rafraichissement mémoire, qui était réalisé par le CPU à l'époque. Ce registre contenait la prochaine adresse mémoire à rafraichir, et était incrémenté à chaque rafraichissement d'une adresse. Et il était lui aussi intégré au séquenceur et incrémenté par l'incrémenteur partagé.
[[File:Organisation interne d'une architecture à pile.png|centre|vignette|upright=2|Organisation interne d'une architecture à pile]]
===Le pointeur de pile adressable explicitement===
Maintenant, étudions le cas d'une pile d'appel, précisément d'une pile d'appel avec des cadres de pile de taille variable. Sous ces conditions, le pointeur de pile est un registre adressable, avec un nom/numéro de registre dédié. Tel est par exemple le cas des processeurs x86 avec le registre ESP (''Extended Stack Pointer''). 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 calculs d'adresse, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres.
Dans ce cas, la meilleure solution est de placer le pointeur de pile dans le banc de registre généraux, avec les autres registres entiers. En faisant cela, la manipulation du pointeur de pile est faite par l'unité de calcul entière, pas besoin d'utiliser un incrémenteur dédiée. Il a existé des processeurs qui mettaient le pointeur de pile dans le banc de registre, mais l'incrémentaient avec un incrémenteur dédié, mais nous les verrons dans le chapitre sur les architectures à accumulateur. La raison est que sur les processeurs concernés, les adresses ne faisaient pas la même taille que les données : c'était des processeurs 8 bits, qui géraient des adresses de 16 bits.
==Annexe : l'implémentation du système d'''aliasing'' des registres des CPU x86==
Il y a quelques chapitres, nous avions parlé du système d'''aliasing'' des registres des CPU x86. Pour rappel, il permet de donner plusieurs noms de registre pour un même registre. Plus précisément, pour un registre 64 bits, le registre complet aura un nom de registre, les 32 bits de poids faible auront leur nom de registre dédié, idem pour les 16 bits de poids faible, etc. Il est possible de faire des calculs sur ces moitiés/quarts/huitièmes de registres sans problème.
===L'''aliasing'' du 8086, pour les registres 16 bits===
[[File:Register 8086.PNG|vignette|Register 8086]]
L'implémentation de l'''aliasing'' est apparue sur les premiers CPU Intel 16 bits, notamment le 8086. En tout, ils avaient quatre registres généraux 16 bits : AX, BX, CX et DX. Ces quatre registres 16 bits étaient coupés en deux octets, chacun adressable. Par exemple, le registre AX était coupé en deux octets nommés AH et AL, chacun ayant son propre nom/numéro de registre. Les instructions d'addition/soustraction pouvaient manipuler le registre AL, ou le registre AH, ce qui modifiait les 8 bits de poids faible ou fort selon le registre choisit.
Le banc de registre ne gére que 4 registres de 16 bits, à savoir AX, BX, CX et DX. Lors d'une lecture d'un registre 8 bits, le registre 16 bit entier est lu depuis le banc de registre, mais les bits inutiles sont ignorés. Par contre, l'écriture peut se faire soit avec 16 bits d'un coup, soit pour seulement un octet. Le port d'écriture du banc de registre peut être configuré de manière à autoriser l'écriture soit sur les 16 bits du registre, soit seulement sur les 8 bits de poids faible, soit écrire dans les 8 bits de poids fort.
[[File:Port d'écriture du banc de registre du 8086.png|centre|vignette|upright=2.5|Port d'écriture du banc de registre du 8086]]
Une opération sur un registre 8 bits se passe comme suit. Premièrement, on lit le registre 16 bits complet depuis le banc de registre. Si l'on a sélectionné l'octet de poids faible, il ne se passe rien de particulier, l'opérande 16 bits est envoyée directement à l'ALU. Mais si on a sélectionné l'octet de poids fort, la valeur lue est décalée de 7 rangs pour atterrir dans les 8 octets de poids faible. Ensuite, l'unité de calcul fait un calcul avec cet opérande, un calcul 16 bits tout ce qu'il y a de plus classique. Troisièmement, le résultat est enregistré dans le banc de registre, en le configurant convenablement. La configuration précise s'il faut enregistrer le résultat dans un registre 16 bits, soit seulement dans l'octet de poids faible/fort.
Afin de simplifier le câblage, les 16 bits des registres AX/BX/CX/DX sont entrelacés d'une manière un peu particulière. Intuitivement, on s'attend à ce que les bits soient physiquement dans le même ordre que dans le registre : le bit 0 est placé à côté du bit 1, suivi par le bit 2, etc. Mais à la place, l'octet de poids fort et de poids faible sont mélangés. Deux bits consécutifs appartiennent à deux octets différents. Le tout est décrit dans le tableau ci-dessous.
{|class="wikitable"
|-
! Registre 16 bits normal
| class="f_bleu" | 15
| class="f_bleu" | 14
| class="f_bleu" | 13
| class="f_bleu" | 12
| class="f_bleu" | 11
| class="f_bleu" | 10
| class="f_bleu" | 9
| class="f_bleu" | 8
| class="f_rouge" | 7
| class="f_rouge" | 6
| class="f_rouge" | 5
| class="f_rouge" | 4
| class="f_rouge" | 3
| class="f_rouge" | 2
| class="f_rouge" | 1
| class="f_rouge" | 0
|-
! Registre 16 bits du 8086
| class="f_bleu" | 15
| class="f_rouge" | 7
| class="f_bleu" | 14
| class="f_rouge" | 6
| class="f_bleu" | 13
| class="f_rouge" | 5
| class="f_bleu" | 12
| class="f_rouge" | 4
| class="f_bleu" | 11
| class="f_rouge" | 3
| class="f_bleu" | 10
| class="f_rouge" | 2
| class="f_bleu" | 9
| class="f_rouge" | 1
| class="f_bleu" | 8
| class="f_rouge" | 0
|}
En faisant cela, le décaleur en entrée de l'ALU est bien plus simple. Il y a 8 multiplexeurs, mais le câblage est bien plus simple. Par contre, en sortie de l'ALU, il faut remettre les bits du résultat dans l'ordre adéquat, celui du registre 8086. Pour cela, les interconnexions sur le port d'écriture sont conçues pour. Il faut juste mettre les fils de sortie de l'ALU sur la bonne entrée, par besoin de multiplexeurs.
===L'''aliasing'' sur les processeurs x86 32/64 bits===
Les processeurs x86 32 et 64 bits ont un système d'''aliasing'' qui complète le système précédent. Les processeurs 32 bits étendent les registres 16 bits existants à 32 bits. Pour ce faire, le registre 32 bit a un nouveau nom de registre, distincts du nom de registre utilisé pour l'ancien registre 16 bits. Il est possible d'adresser les 16 bits de poids faible de ce registre, avec le même nom de registre que celui utilisé pour le registre 16 sur les processeurs d'avant. Même chose avec les processeurs 64, avec l'ajout d'un nouveau nom de registre pour adresser un registre de 64 bit complet.
En soit, implémenter ce système n'est pas compliqué. Prenons le cas du registre RAX (64 bits), et de ses subdivisions nommées EAX (32 bits), AX (16 bits). À l'intérieur du banc de registre, il n'y a que le registre RAX. Le banc de registre ne comprend qu'un seul nom de registre : RAX. Les subdivisions EAX et AX n'existent qu'au niveau de l'écriture dans le banc de registre. L'écriture dans le banc de registre est configurable, de manière à ne modifier que les bits adéquats. Le résultat d'un calcul de l'ALU fait 64 bits, il est envoyé sur le port d'écriture. À ce niveau, soit les 64 bits sont écrits dans le registre, soit seulement les 32/16 bits de poids faible. Le système du 8086 est préservé pour les écritures dans les 16 bits de poids faible.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les composants d'un processeur
| prevText=Les composants d'un processeur
| next=L'unité de chargement et le program counter
| nextText=L'unité de chargement et le program counter
}}
</noinclude>
lbsdcteb1km2pfav8kwtf0ijruxhgrn
772059
772058
2026-09-11T23:21:42Z
Mewtow
31375
/* L'unité de calcul d'adresse */
772059
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
: Rappelons que dans ce chapitre, ainsi que dans les quatre chapitres suivants, nous ne parlons que des architectures à registres généraux. Aussi, nous mettons de côté les anciens processeurs à accumulateur et les CPU 8 bits, commercialisés avant l'arrivée des registres généraux, ainsi que certaines architectures non-conventionnelles comme les DSPs, qui ont des unités de calcul d'adresse dédiées. Mais nous ne parlerons pas de celles-ci dans ce chapitre.
L'AGU est utilisée pour certains modes d'adressage, à savoir ceux qui demandent de combiner une adresse avec soit un indice, soit un décalage, plus rarement les deux. Les calculs d'adresse prennent une adresse de base, et lui ajoutent un indice ou un décalage. Il faut parfois décaler l'indice, de quelques rangs. Pas besoin de multiplications, de divisions, ou d'autres opération plus complexe : décalages et additions/soustractions suffisent. L'AGU est donc beaucoup plus simple qu'une ALU normale et se résume à un additionneur-soustracteur couplé à un décaleur. Son cout en circuits est ridicule.
Mais pourquoi ajouter une AGU, alors que l'ALU entière suffit largement pour implémenter les modes d'adressage complexes. Il est parfaitement possible de faire les calculs d'adresse dans l'ALU entière, puis d'envoyer le résultat à l'unité mémoire. Sauf que faire ainsi pose plusieurs problèmes. Premièrement, cela complexifie le travail du séquenceur, qui doit enchainer deux micro-opérations. Ensuite, cela demande de câbler un chemin reliant la sortie de l'ALU entière à l'unité mémoire. Rien d'impossible, on verra même cela en détail à la fin du chapitre. Mais cette connexion est souvent difficile à câbler.
En ajoutant une AGU, les deux défauts disparaissent. La liaison entre ALU et unité mémoire disparait, l'unité mémoire n'est reliée qu'aux registres. Le séquenceur est aussi simplifié, vu que les accès mémoire ne font plus qu'une seule micro-opération, il n'y a plus besoin d'utiliser une micro-opération pour les calculs d'adresse. Le câblage du processeur est simplifié, au prix d'un cout modéré en transistors. On peut même obtenir nune économie, les simplifications du séquenceur sur-compensant le cout en transistors de l'AGU.
Parlons rapidement de l'implémentation du mode d'adressage "Base + Indice + Décalage", qui additionne une adresse de base, un indice (mis à l'échelle) et un décalage (une constante immédiate). Il demande d'additionner trois opérandes, chose que l'ALU entière ne sait pas faire (sauf exceptions). En théorie, une AGU peut réaliser ce calcul sans cout, en utilisant un additionneur ''carry save'', capable d'additionner trois opérandes à la fois. L'AGU est alors implémentée avec un circuit décaleur, pour décaler l'indice, et un additionneur trois-opérandes. Le résultat est que l'on peut faire les calculs d'adresse en un seul cycle d'horloge, pour un cout en hardware modéré.
[[File:Unité de calcul d'adresse.png|centre|vignette|upright=2|Unité de calcul d'adresse]]
Chose étrange, ce n'est pas ce qui était fait sur les premiers processeurs Intel. Sur ces CPUs, le calcul de l'adresse se faisait en deux cycles d'horloge. Lors du premier cycle d'horloge, l'ALU entière faisait le calcul d'adresse "Base + Indice". Lors du second cycle, le résultat de l'addition précédente est additionné avec le décalage, dans l'AGU. Pour cela, l'AGU était reliée à l'ALU entière et à l'unité de contrôle. Difficile de comprendre cette décision, l'implémentation ne profitant que partiellement des avantages mentionnés plus haut pour ce mode d'adressage.
===Le rafraichissement mémoire optimisé et le contrôleur mémoire intégré===
Depuis les années 80, les processeurs sont souvent combinés avec une mémoire principale de type DRAM. De telles mémoires doivent être rafraichies régulièrement pour ne pas perdre de données. Le rafraichissement se fait généralement adresse par adresse, ou ligne par ligne (les lignes sont des super-bytes internes à la DRAM). Le rafraichissement est en théorie géré par le contrôleur mémoire installé sur la carte mère. Mais au tout début de l'informatique, du temps des processeurs 8 bits, le rafraichissement mémoire était géré directement par le processeur.
Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le processeur 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''. Et tout cela était intégré à l'unité d'accès mémoire.
Depuis les années 2000, les processeurs modernes ont un contrôleur mémoire DRAM intégré directement dans le processeur. Ce qui fait qu'ils gèrent non seulement le rafraichissement, mais aussi d'autres fonctions bien pus complexes.
===L'interface de l'unité mémoire===
Vu de l'extérieur, l'unité mémoire ressemble à n'importe quel circuit électronique, avec des entrées et des sorties. L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie. Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. Si l'unité mémoire incorpore une AGU, on envoie aussi les indices et autres données sur le port d'entrée. Le port de sortie est utilisé pour récupérer le résultat des lectures. Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues.
L'unité mémoire est connectée au reste du processeur grâce à un réseau d'interconnexion qu'on étudiera plus loin. Les connexions principales sont celles avec les registres : les adresses à lire/écrire sont souvent lues depuis les registres, les lectures copient une donnée dans un registre. Il peut y avoir une connexion avec l'unité de calcul pour les opérations ''load-up'', ou pour le calcul d'adresse, mais c'est secondaire.
[[File:Unité d'accès mémoire LOAD-STORE.png|centre|vignette|upright=2|Unité d'accès mémoire LOAD-STORE.]]
===Un exemple : l'unité mémoire du 8086 et ses dérivés===
Après avoir vu la théorie, voyons un exemple du monde réel. Nous allons étudier l'exemple du processeur Intel 8086 et de son dérivé, le 80186. Ces processeurs avaient une particularité : leur unité mémoire était fortement séparée du reste du processeur. Le processeur était séparé en une ''Bus Interface Unit'' et une ''Execution Unit''. L'''Execution Unit'' a un nom trompeur : c'est l'ensemble du processeur, unité mémoire exclue. La ''Bus Interface Unit'' est l'unité mémoire. Elle se charge à la fois du chargement des données et des instructions.
Cependant, nous n'allons pouvoir détailler cette unité dans le détail, car nous n'avons pas encore vu la mémoire virtuelle, ni le préchargement. Mais cela ne nous gênera pas, vous aurez simplement quelques détails passés sous silence pour ce qui est de l'unité de calcul. Toujours est-il que voici à quoi ressemble l'intérieur d'un 80186. Elle contient :
* des registres d'adresse utilisés pour ses fonctionnalités de segmentation ;
* une unité de calcul d'adresse utilisée pour les calculs d'adresse ;
* une interface avec le bus mémoire, qu'on détaillera plus bas ;
* une mémoire FIFO où les instructions sont chargées en avance, appelée la ''Prefetch Input Queue''.
[[File:80186 arch.png|centre|vignette|upright=2.5|Microarchitecture du 80186.]]
Nous détaillerons la ''Prefetch Input Queue'' dans le chapitre sur le préchargement. Les registres et l'unité de calcul seront détaillés dans le chapitre sur la mémoire virtuelle. Il est intéressant de regarder l'interface avec le bus mémoire. Cette interface contient les registres d’interfaçage, ainsi que des circuits annexes.
Pour comprendre ce qu'elle fait, il faut savoir que le processeur communique avec la mémoire avec une séquence très précise, qui dure exactement quatre cycles. Les quatre cycles sont appelés des ''T-states'' et ils sont numérotés de 1 à 4 : T1, T2, T3 et T4.
Pour une lecture, l'adresse est envoyée lors du premier cycle, elle est disponible sur le bus au troisième cycle. Les cycles 1 et 3 sont inutilisés, le processeur se déconnecte du bus lors de ces cycles.
{|class="wikitable"
|-
! T1 !! T2 !! T3 !! T4
|-
| Adresse || || Donnée lue ||
|}
Pour une lecture, l'adresse est envoyée lors du premier cycle, la donnée est envoyée sur le bus lors des trois cycles suivants.
{|class="wikitable"
! T1 !! T2 !! T3 !! T4
|-
| Adresse || colspan="3 | Donnée à écrire
|}
Les ''timings'' vus plus haut ne sont effectivement observés que si la mémoire est assez rapide. Mais le 8086 pouvait être combiné avec des mémoires RAM assez diverses, certaines plus lentes que d'autres. Si la mémoire est trop lente, elle peut prendre plusieurs cycles avant de renvoyer la donnée lue ou d'accepter la donnée à écrire. Pour gérer la situation, le processeur pouvait attendre que la mémoire réponde, avec des ''wait states''.
Pour cela, la mémoire prévenait quand la donnée lue était disponible, ou quand une écriture était terminée. Le processeur avait une entrée READY qui indiquait que la mémoire était disponible ou qu'elle avait terminé son travail. Lors d'une lecture, la mémoire mettait cette entrée READY à 1 quand la donnée lue était disponible sur le bus mémoire. Idem pour une écriture. L'implémentation dans l'unité mémoire est très simple : le processeur restait dans le cycle T3 en attendant que l'entrée READY soit à 1. Lors d'une écriture, cela maintenait à donnée à écrire sur le bus mémoire. Pour une lecture, cela permettait de laisser le registre d’interfaçage des données connecté au bus mémoire, en attendant la donnée lue.
Nous venons de voir que le processeur pouvait rester dans l'état T3 si le besoin s'en fait sentir. Mais cette astuce est utilisée dans d'autres situations. Par exemple, le processeur dispose d'une instruction HALT, qui le déconnecte totalement du bus. Le processeur reste déconnecté tant qu'il ne reçoit pas un RESET ou une interruption. Il s'agit d'une forme limitée de mise en veille, qui n'utilisait pas de méthodes pour économiser de l'énergie. L'instruction HALT était implémentée en restant en permanence dans l'état T2. Le processeur attendait d'avoir terminé toute instruction précédente, puis lançait une pseudo-lecture et restait en état T1 en attendant une interruption ou un RESET.
Le registre d’interfaçage pour les adresses est connecté sur le bus mémoire lors du premier cycle, celui pour les données l'est à des cycles différents suivant l'opération demandée. La connexion/déconnexion des registres d’interfaçage se fait en utilisant des circuits trois-états. Il y a une couche de circuits trois-états entre le registre d’interfaçage et le bus mémoire, qui permet de connecter/déconnecter les registres du bus mémoire, mais aussi d'imposer le sens de transfert suivant qu'on fasse une lecture ou une écriture.
[[File:Circuit d'interfacage mémoire du 8086.png|centre|vignette|upright=2|Circuit d’interfaçage mémoire du 8086.]]
Le circuit en question est un circuit séquentiel assez simple, une petite "machine à état" comme on le dit en termes techniques. Le circuit séquentiel commande les circuits trois-états. Une implémentation naïve serait d'utiliser un compteur pouvant compter de 0 à 3, avec des comparateurs. Il faut, en tout :
* Un comparateur qui détermine si le compteur est au cycle 1. Si c'est le cas, il connecte le registre d’interfaçage d'adresse sur le bus mémoire.
* Un autre comparateur détermine si une lecture est en cours et que l'on est dans le troisième cycle. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode lecture.
* Un autre comparateur détermine si une écriture est en cours et que l'on est dans les cycles adéquats. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode écriture.
Afin de simplifier fortement le circuit, le compteur utilisé utilise la représentation ''one hot''. Pour rappel, cela veut dire qu'il y a une bascule pour chaque valeur que peut prendre le compteur. Le compteur ''one hot'' est composé de quatre bascules. La première bascule est à 1 pour le cycle T1, la seconde est à 1 pour le cycle T2, la troisième est à 1 pour le cycle T3, la quatrième l'est pour le cycle T4. Ainsi, on peut se passer de certains comparateurs. Par exemple, le comparateur pour l'adresse disparait : la bascule commande directement les circuits trois-états. Les deux autres comparateurs se transforment des circuits à respectivement une ou quatre portes logiques.
Incrémenter le compteur se fait cependant sous certaines conditions. La machine à état contient des circuits qui décident quand incrémenter le compteur. Pour cela, il y a un circuit qui détermine quand on peut passer du cycle T1 au cycle T2, un autre pour le passage de T2 à T3, etc. Les circuits en question sont un paquet de portes logiques assez simple. Ils reçoivent des signaux de commande provenant du séquenceur, ainsi que des entrées comme l'entrée READY pour les ''wait states''.
[[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|centre|vignette|upright=2|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]]
Maintenant, parlons des problématiques liées au calcul d'adresse. Tous les accès mémoire demandent de faire un calcul d'adresse, en raison de l'usage de la fonctionnalité de segmentation. Et ce calcul d'adresse prend deux cycles. L'adresse est calculée, puis que l'accès mémoire est réalisé. En tout, cela fait 6 cycles : 2 pour le calcul d'adresse, puis les cycles T1, T2, T3 et T4.
Une optimisation importante est que deux accès mémoire peuvent se recouvrir. A savoir que pendant qu'un accès mémoire a lieu sur le bus mémoire, on calcule l'adresse de l'accès du suivant. Les deux ont lieu dans des circuits séparés, ce qui fait que c'est possible. Pour donner un exemple, on peut calculer l'adresse de la prochaine instruction, pendant que l'instruction mémoire en cours est exécutée. Le séquenceur du processeur est configuré pour démarrer le calcul d'adresse au bon moment. Il attend que le compteur soit dans l'état T3 et T4.
{|class="wikitable"
|-
! Unité de calcul
| class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || || || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || || || ||
|-
! Bus mémoire
| || || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2
|}
: La machine à état est décrite en détail dans cet article de blog : [https://www.righto.com/2024/04/intel-8088-bus-state-machine.html Talking to memory: Inside the Intel 8088 processor's bus interface state machine ].
==Le chemin de données et son réseau d'interconnexions==
Nous venons de voir que le chemin de données contient une unité de calcul (parfois plusieurs), des registres isolés, un banc de registre, une unité mémoire. Le tout est chapeauté par une unité de contrôle qui commande le chemin de données, qui fera l'objet des prochains chapitres. Mais il faut maintenant relier registres, ALU et unité mémoire pour que l'ensemble fonctionne. Pour cela, diverses interconnexions internes au processeur se chargent de relier le tout.
Sur les anciens processeurs, les interconnexions sont assez simples et se résument à un ou deux '''bus internes au processeur''', reliés au bus mémoire. C'était la norme sur des architectures assez ancienne, qu'on n'a pas encore vu à ce point du cours, appelées les architectures à accumulateur et à pile. Mais ce n'est plus la solution utilisée actuellement. De nos jours, le réseaux d'interconnexion intra-processeur est un ensemble de connexions point à point entre ALU/registres/unité mémoire. Et paradoxalement, cela rend plus facile de comprendre ce réseau d'interconnexion.
===Introduction propédeutique : l'implémentation des modes d'adressage principaux===
L'organisation interne du processeur dépend fortement des modes d'adressage supportés. Pour simplifier les explications, nous allons séparer les modes d'adressage qui gèrent les pointeurs et les autres. Suivant que le processeur supporte les pointeurs ou non, l'organisation des bus interne est légèrement différente. La différence se voit sur les connexions avec le bus d'adresse et de données.
Tout processeur gère au minimum le '''mode d'adressage absolu''', où l'adresse est intégrée à l'instruction. Le séquenceur extrait l'adresse mémoire de l'instruction, et l'envoie sur le bus d'adresse. Pour cela, le séquenceur est relié au bus d'adresse, le chemin de donnée est relié au bus de données. Le chemin de donnée n'est pas connecté au bus d'adresse, il n'y a pas d'autres connexions.
[[File:Chemin de données sans support des pointeurs.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs]]
Le '''support des pointeurs''' demande d'intégrer des modes d'adressage dédiés : l'adressage indirect à registre, l'adresse base + indice, et les autres. Les pointeurs sont stockés dans le banc de registre et sont modifiés par l'unité de calcul. Pour supporter les pointeurs, le chemin de données est connecté sur le bus d'adresse avec le séquenceur. Suivant le mode d'adressage, le bus d'adresse est relié soit au chemin de données, soit au séquenceur.
[[File:Chemin de données avec support des pointeurs.png|centre|vignette|upright=2|Chemin de données avec support des pointeurs]]
Pour terminer, il faut parler des instructions de '''copie mémoire vers mémoire''', qui copient une donnée d'une adresse mémoire vers une autre. Elles ne se passent pas vraiment dans le chemin de données, mais se passent purement au niveau des registres d’interfaçage. L'usage d'un registre d’interfaçage unique permet d'implémenter ces instructions très facilement. Elle se fait en deux étapes : on copie la donnée dans le registre d’interfaçage, on l'écrit en mémoire RAM. L'adresse envoyée sur le bus d'adresse n'est pas la même lors des deux étapes.
===Le banc de registre est multi-port, pour gérer nativement les opérations dyadiques===
Les architectures RISC et CISC incorporent un banc de registre, qui est connecté aux unités de calcul et au bus mémoire. Et ce banc de registre peut être mono-port ou multiport. S'il a existé d'anciennes architectures utilisant un banc de registre mono-port, elles sont actuellement obsolètes. Nous les aborderons dans un chapitre dédié aux architectures dites canoniques, mais nous pouvons les laisser de côté pour le moment. De nos jours, tous les processeurs utilisent un banc de registre multi-port.
[[File:Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres).png|centre|vignette|upright=2|Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres)]]
Le banc de registre multiport est optimisé pour les opérations dyadiques. Il dispose précisément de deux ports de lecture et d'un port d'écriture pour l'écriture. Un port de lecture par opérande et le port d'écriture pour enregistrer le résultat. En clair, le processeur peut lire deux opérandes et écrire un résultat en un seul cycle d'horloge. L'avantage est que les opérations simples ne nécessitent qu'une micro-opération, pas plus.
[[File:ALU data paths.svg|centre|vignette|upright=1.5|Processeur LOAD-STORE avec un banc de registre multiport, avec les trois ports mis en évidence.]]
===Une architecture LOAD-STORE basique, avec adressage absolu===
Voyons maintenant comment l'implémentation d'une architecture RISC très simple, qui ne supporte pas les adressages pour les pointeurs, juste les adressages inhérent (à registres) et absolu (par adresse mémoire). Les instructions LOAD et STORE utilisent l'adressage absolu, géré par le séquenceur, reste à gérer l'échange entre banc de registres et bus de données. Une lecture LOAD relie le bus de données au port d'écriture du banc de registres, alors que l'écriture relie le bus au port de lecture du banc de registre. Pour cela, il faut ajouter des multiplexeurs sur les chemins existants, comme illustré par le schéma ci-dessous.
[[File:Bus interne au processeur sur archi LOAD STORE avec banc de registres multiport.png|centre|vignette|upright=2|Organisation interne d'une architecture LOAD STORE avec banc de registres multiport. Nous n'avons pas représenté les signaux de commandes envoyés par le séquenceur au chemin de données.]]
Ajoutons ensuite les instructions de copie entre registres, souvent appelées instruction COPY ou MOV. Elles existent sur la plupart des architectures LOAD-STORE. Une première solution boucle l'entrée du banc de registres sur son entrée, ce qui ne sert que pour les copies de registres.
[[File:Chemin de données d'une architecture LOAD-STORE.png|centre|vignette|upright=2|Chemin de données d'une architecture LOAD-STORE]]
Mais il existe une seconde solution, qui ne demande pas de modifier le chemin de données. Il est possible de faire passer les copies de données entre registres par l'ALU. Lors de ces copies, l'ALU une opération ''Pass through'', à savoir qu'elle recopie une des opérandes sur sa sortie. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, dans le sens où cela permet d'économiser des multiplexeurs. Mais nous verrons cela sous peu. D'ailleurs, dans la suite du chapitre, nous allons partir du principe que les copies entre registres passent par l'ALU, afin de simplifier les schémas.
===L'ajout des modes d'adressage indirects à registre pour les pointeurs===
Passons maintenant à l'implémentation des modes d'adressages pour les pointeurs. Avec eux, l'adresse mémoire à lire/écrire n'est pas intégrée dans une instruction, mais est soit dans un registre, soit calculée par l'ALU.
Le premier mode d'adressage de ce type est le mode d'adressage indirect à registre, où l'adresse à lire/écrire est dans un registre. L'implémenter demande donc de connecter la sortie du banc de registres au bus d'adresse. Il suffit d'ajouter un MUX en sortie d'un port de lecture.
[[File:Chemin de données à trois bus.png|centre|vignette|upright=2|Chemin de données à trois bus.]]
Le mode d'adressage base + indice est un mode d'adressage où l'adresse à lire/écrire est calculée à partir d'une adresse et d'un indice, tous deux présents dans un registre. Le calcul de l'adresse implique au minimum une addition et donc l'ALU. Dans ce cas, on doit connecter la sortie de l'unité de calcul au bus d'adresse.
[[File:Bus avec adressage base+index.png|centre|vignette|upright=2|Bus avec adressage base+index]]
Le chemin de données précédent gère aussi le mode d'adressage indirect avec pré-décrément. Pour rappel, ce mode d'adressage est une variante du mode d'adressage indirect, qui utilise une pointeur/adresse stocké dans un registre. La différence est que ce pointeur est décrémenté avant d'être envoyé sur le bus d'adresse. L'implémentation matérielle est la même que pour le mode Base + Indice : l'adresse est lue depuis les registres, décrémentée dans l'ALU, et envoyée sur le bus d'adresse.
Le schéma précédent montre que le bus d'adresse est connecté à un MUX avant l'ALU et un autre MUX après. Mais il est possible de se passer du premier MUX, utilisé pour le mode d'adressage indirect à registre. La condition est que l'ALU supporte l'opération ''pass through'', un NOP, qui recopie une opérande sur sa sortie. L'ALU fera une opération NOP pour le mode d'adressage indirect à registre, un calcul d'adresse pour le mode d'adressage base + indice. Par contre, faire ainsi rendra l'adressage indirect légèrement plus lent, vu que le temps de passage dans l'ALU sera compté.
[[File:Bus avec adressage indirect.png|centre|vignette|upright=2|Bus avec adressages pour les pointeurs, simplifié.]]
Dans ce qui va suivre, nous allons partir du principe que le processeur est implémenté en suivant le schéma précédent, afin d'avoir des schéma plus lisibles.
===L'adressage immédiat et les modes d'adressages exotiques===
Passons maintenant au mode d’adressage immédiat, qui permet de préciser une constante dans une instruction directement. La constante est extraite de l'instruction par le séquenceur, puis insérée au bon endroit dans le chemin de données. Pour les opérations arithmétiques/logiques/branchements, il faut insérer la constante extraite sur l'entrée de l'ALU. Sur certains processeurs, la constante peut être négative et doit alors subir une extension de signe dans un circuit spécialisé.
[[File:Chemin de données - Adressage immédiat avec extension de signe.png|centre|vignette|upright=2|Chemin de données - Adressage immédiat avec extension de signe.]]
L'implémentation précédente gère aussi les modes d'adressage base + décalage et absolu indexé. Pour rappel, le premier ajoute une constante à une adresse prise dans les registres, le second prend une adresse constante et lui ajoute un indice pris dans les registres. Dans les deux cas, on lit un registre, extrait une constante/adresse de l’instruction, additionne les deux dans l'ALU, avant d'envoyer le résultat sur le bus d'adresse. La seule difficulté est de désactiver l'extension de signe pour les adresses.
Le mode d'adressage absolu peut être traité de la même manière, si l'ALU est capable de faire des NOPs. L'adresse est insérée au même endroit que pour le mode d'adressage immédiat, parcours l'unité de calcul inchangée parce que NOP, et termine sur le bus d'adresse.
[[File:Chemin de données avec une ALU capable de faire des NOP.png|centre|vignette|upright=2|Chemin de données avec adressage immédiat étendu pour gérer des adresses.]]
Passons maintenant au cas particulier d'une instruction MOV qui copie une constante dans un registre. Il n'y a rien à faire si l'unité de calcul est capable d'effectuer une opération NOP/''pass through''. Pour charger une constante dans un registre, l'ALU est configurée pour faire un NOP, la constante traverse l'ALU et se retrouve dans les registres. Si l'ALU ne gère pas les NOP, la constante doit être envoyée sur l'entrée d'écriture du banc de registres, à travers un MUX dédié.
[[File:Implémentation de l'adressage immédiat dans le chemin de données.png|centre|vignette|upright=2|Implémentation de l'adressage immédiat dans le chemin de données]]
===Les architectures CISC : les opérations ''load-op''===
Tout ce qu'on a vu précédemment porte sur les processeurs de type LOAD-STORE, souvent confondus avec les processeurs de type RISC, où les accès mémoire sont séparés des instructions utilisant l'ALU. Il est maintenant temps de voir les processeurs CISC, qui gèrent des instructions ''load-op'', qui peuvent lire une opérande depuis la mémoire.
L'implémentation des opérations ''load-op'' relie le bus de donnée directement sur une entrée de l'unité de calcul, en utilisant encore une fois un multiplexeur. L'implémentation parait simple, mais c'est parce que toute la complexité est déportée dans le séquenceur. C'est lui qui se charge de détecter quand la lecture de l'opérande est terminée, quand l'opérande est disponible.
Les instructions ''load-op'' s'exécutent en plusieurs étapes, en plusieurs micro-opérations. Il y a typiquement une étape pour l'opérande à lire en mémoire et une étape de calcul. L'usage d'un registre d’interfaçage permet d'implémenter les instructions ''load-op'' très facilement. Une opération ''load-op'' charge l'opérande en mémoire dans un registre d’interfaçage, puis relier ce registre d’interfaçage sur une des entrées de l'ALU. Un simple multiplexeur suffit pour implémenter le tout, en plus des modifications adéquates du séquenceur.
[[File:Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire.png|centre|vignette|upright=2|Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire]]
Supporter les instructions multi-accès (qui font plusieurs accès mémoire) ne modifie pas fondamentalement le réseau d'interconnexion, ni le chemin de données La raison est que supporter les instructions multi-accès se fait au niveau du séquenceur. En réalité, les accès mémoire se font en série, l'un après l'autre, sous la commande du séquenceur qui émet plusieurs micro-opérations mémoire consécutives. Les données lues sont placées dans des registres d’interactivement mémoire, ce qui demande d'ajouter des registres d’interfaçage mémoire en plus.
==Annexe : le cas particulier du pointeur de pile==
Le pointeur de pile est un registre un peu particulier. Il peut être placé dans le chemin de données ou dans le séquenceur, voire dans l'unité de chargement, tout dépend du processeur. Tout dépend de si le pointeur de pile gère une pile d'adresses de retour ou une pile d'appel.
===Le pointeur de pile non-adressable explicitement===
Avec une pile d'adresse de retour, le pointeur de pile n'est pas adressable explicitement, il est juste adressé implicitement par des instructions d'appel de fonction CALL et des instructions de retour de fonction RET. Le pointeur de pile est alors juste incrémenté ou décrémenté par un pas constant, il ne subit pas d'autres opérations, son adressage est implicite. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Il n'y a pas besoin de le relier au chemin de données, vu qu'il n'échange pas de données avec les autres registres. Il y a alors plusieurs solutions, mais la plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Quelques processeurs simples disposent d'une pile d'appel très limitée, où le pointeur de pile n'est pas adressable explicitement. Il est adressé implicitement par les instruction CALL, RET, mais aussi PUSH et POP, mais aucune autre instruction ne permet cela. Là encore, le pointeur de pile ne communique pas avec les autres registres. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Là encore, le plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Dans les deux cas, le pointeur de pile est placé dans l'unité de contrôle, le séquenceur, et est associé à un incrémenteur dédié. Il se trouve que cet incrémenteur est souvent partagé avec le ''program counter''. En effet, les deux sont des adresses mémoire, qui sont incrémentées et décrémentées par pas constants, ne subissent pas d'autres opérations (si ce n'est des branchements, mais passons). Les ressemblances sont suffisantes pour fusionner les deux circuits. Ils peuvent donc avoir un '''incrémenteur partagé'''.
L'incrémenteur en question est donc partagé entre pointeur de pile, ''program counter'' et quelques autres registres similaires. Par exemple, le Z80 intégrait un registre pour le rafraichissement mémoire, qui était réalisé par le CPU à l'époque. Ce registre contenait la prochaine adresse mémoire à rafraichir, et était incrémenté à chaque rafraichissement d'une adresse. Et il était lui aussi intégré au séquenceur et incrémenté par l'incrémenteur partagé.
[[File:Organisation interne d'une architecture à pile.png|centre|vignette|upright=2|Organisation interne d'une architecture à pile]]
===Le pointeur de pile adressable explicitement===
Maintenant, étudions le cas d'une pile d'appel, précisément d'une pile d'appel avec des cadres de pile de taille variable. Sous ces conditions, le pointeur de pile est un registre adressable, avec un nom/numéro de registre dédié. Tel est par exemple le cas des processeurs x86 avec le registre ESP (''Extended Stack Pointer''). 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 calculs d'adresse, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres.
Dans ce cas, la meilleure solution est de placer le pointeur de pile dans le banc de registre généraux, avec les autres registres entiers. En faisant cela, la manipulation du pointeur de pile est faite par l'unité de calcul entière, pas besoin d'utiliser un incrémenteur dédiée. Il a existé des processeurs qui mettaient le pointeur de pile dans le banc de registre, mais l'incrémentaient avec un incrémenteur dédié, mais nous les verrons dans le chapitre sur les architectures à accumulateur. La raison est que sur les processeurs concernés, les adresses ne faisaient pas la même taille que les données : c'était des processeurs 8 bits, qui géraient des adresses de 16 bits.
==Annexe : l'implémentation du système d'''aliasing'' des registres des CPU x86==
Il y a quelques chapitres, nous avions parlé du système d'''aliasing'' des registres des CPU x86. Pour rappel, il permet de donner plusieurs noms de registre pour un même registre. Plus précisément, pour un registre 64 bits, le registre complet aura un nom de registre, les 32 bits de poids faible auront leur nom de registre dédié, idem pour les 16 bits de poids faible, etc. Il est possible de faire des calculs sur ces moitiés/quarts/huitièmes de registres sans problème.
===L'''aliasing'' du 8086, pour les registres 16 bits===
[[File:Register 8086.PNG|vignette|Register 8086]]
L'implémentation de l'''aliasing'' est apparue sur les premiers CPU Intel 16 bits, notamment le 8086. En tout, ils avaient quatre registres généraux 16 bits : AX, BX, CX et DX. Ces quatre registres 16 bits étaient coupés en deux octets, chacun adressable. Par exemple, le registre AX était coupé en deux octets nommés AH et AL, chacun ayant son propre nom/numéro de registre. Les instructions d'addition/soustraction pouvaient manipuler le registre AL, ou le registre AH, ce qui modifiait les 8 bits de poids faible ou fort selon le registre choisit.
Le banc de registre ne gére que 4 registres de 16 bits, à savoir AX, BX, CX et DX. Lors d'une lecture d'un registre 8 bits, le registre 16 bit entier est lu depuis le banc de registre, mais les bits inutiles sont ignorés. Par contre, l'écriture peut se faire soit avec 16 bits d'un coup, soit pour seulement un octet. Le port d'écriture du banc de registre peut être configuré de manière à autoriser l'écriture soit sur les 16 bits du registre, soit seulement sur les 8 bits de poids faible, soit écrire dans les 8 bits de poids fort.
[[File:Port d'écriture du banc de registre du 8086.png|centre|vignette|upright=2.5|Port d'écriture du banc de registre du 8086]]
Une opération sur un registre 8 bits se passe comme suit. Premièrement, on lit le registre 16 bits complet depuis le banc de registre. Si l'on a sélectionné l'octet de poids faible, il ne se passe rien de particulier, l'opérande 16 bits est envoyée directement à l'ALU. Mais si on a sélectionné l'octet de poids fort, la valeur lue est décalée de 7 rangs pour atterrir dans les 8 octets de poids faible. Ensuite, l'unité de calcul fait un calcul avec cet opérande, un calcul 16 bits tout ce qu'il y a de plus classique. Troisièmement, le résultat est enregistré dans le banc de registre, en le configurant convenablement. La configuration précise s'il faut enregistrer le résultat dans un registre 16 bits, soit seulement dans l'octet de poids faible/fort.
Afin de simplifier le câblage, les 16 bits des registres AX/BX/CX/DX sont entrelacés d'une manière un peu particulière. Intuitivement, on s'attend à ce que les bits soient physiquement dans le même ordre que dans le registre : le bit 0 est placé à côté du bit 1, suivi par le bit 2, etc. Mais à la place, l'octet de poids fort et de poids faible sont mélangés. Deux bits consécutifs appartiennent à deux octets différents. Le tout est décrit dans le tableau ci-dessous.
{|class="wikitable"
|-
! Registre 16 bits normal
| class="f_bleu" | 15
| class="f_bleu" | 14
| class="f_bleu" | 13
| class="f_bleu" | 12
| class="f_bleu" | 11
| class="f_bleu" | 10
| class="f_bleu" | 9
| class="f_bleu" | 8
| class="f_rouge" | 7
| class="f_rouge" | 6
| class="f_rouge" | 5
| class="f_rouge" | 4
| class="f_rouge" | 3
| class="f_rouge" | 2
| class="f_rouge" | 1
| class="f_rouge" | 0
|-
! Registre 16 bits du 8086
| class="f_bleu" | 15
| class="f_rouge" | 7
| class="f_bleu" | 14
| class="f_rouge" | 6
| class="f_bleu" | 13
| class="f_rouge" | 5
| class="f_bleu" | 12
| class="f_rouge" | 4
| class="f_bleu" | 11
| class="f_rouge" | 3
| class="f_bleu" | 10
| class="f_rouge" | 2
| class="f_bleu" | 9
| class="f_rouge" | 1
| class="f_bleu" | 8
| class="f_rouge" | 0
|}
En faisant cela, le décaleur en entrée de l'ALU est bien plus simple. Il y a 8 multiplexeurs, mais le câblage est bien plus simple. Par contre, en sortie de l'ALU, il faut remettre les bits du résultat dans l'ordre adéquat, celui du registre 8086. Pour cela, les interconnexions sur le port d'écriture sont conçues pour. Il faut juste mettre les fils de sortie de l'ALU sur la bonne entrée, par besoin de multiplexeurs.
===L'''aliasing'' sur les processeurs x86 32/64 bits===
Les processeurs x86 32 et 64 bits ont un système d'''aliasing'' qui complète le système précédent. Les processeurs 32 bits étendent les registres 16 bits existants à 32 bits. Pour ce faire, le registre 32 bit a un nouveau nom de registre, distincts du nom de registre utilisé pour l'ancien registre 16 bits. Il est possible d'adresser les 16 bits de poids faible de ce registre, avec le même nom de registre que celui utilisé pour le registre 16 sur les processeurs d'avant. Même chose avec les processeurs 64, avec l'ajout d'un nouveau nom de registre pour adresser un registre de 64 bit complet.
En soit, implémenter ce système n'est pas compliqué. Prenons le cas du registre RAX (64 bits), et de ses subdivisions nommées EAX (32 bits), AX (16 bits). À l'intérieur du banc de registre, il n'y a que le registre RAX. Le banc de registre ne comprend qu'un seul nom de registre : RAX. Les subdivisions EAX et AX n'existent qu'au niveau de l'écriture dans le banc de registre. L'écriture dans le banc de registre est configurable, de manière à ne modifier que les bits adéquats. Le résultat d'un calcul de l'ALU fait 64 bits, il est envoyé sur le port d'écriture. À ce niveau, soit les 64 bits sont écrits dans le registre, soit seulement les 32/16 bits de poids faible. Le système du 8086 est préservé pour les écritures dans les 16 bits de poids faible.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les composants d'un processeur
| prevText=Les composants d'un processeur
| next=L'unité de chargement et le program counter
| nextText=L'unité de chargement et le program counter
}}
</noinclude>
fvqpoksh5e9o7l91qyte74e02llfgmf
772060
772059
2026-09-11T23:24:36Z
Mewtow
31375
/* L'ajout des modes d'adressage indirects à registre pour les pointeurs */
772060
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
: Rappelons que dans ce chapitre, ainsi que dans les quatre chapitres suivants, nous ne parlons que des architectures à registres généraux. Aussi, nous mettons de côté les anciens processeurs à accumulateur et les CPU 8 bits, commercialisés avant l'arrivée des registres généraux, ainsi que certaines architectures non-conventionnelles comme les DSPs, qui ont des unités de calcul d'adresse dédiées. Mais nous ne parlerons pas de celles-ci dans ce chapitre.
L'AGU est utilisée pour certains modes d'adressage, à savoir ceux qui demandent de combiner une adresse avec soit un indice, soit un décalage, plus rarement les deux. Les calculs d'adresse prennent une adresse de base, et lui ajoutent un indice ou un décalage. Il faut parfois décaler l'indice, de quelques rangs. Pas besoin de multiplications, de divisions, ou d'autres opération plus complexe : décalages et additions/soustractions suffisent. L'AGU est donc beaucoup plus simple qu'une ALU normale et se résume à un additionneur-soustracteur couplé à un décaleur. Son cout en circuits est ridicule.
Mais pourquoi ajouter une AGU, alors que l'ALU entière suffit largement pour implémenter les modes d'adressage complexes. Il est parfaitement possible de faire les calculs d'adresse dans l'ALU entière, puis d'envoyer le résultat à l'unité mémoire. Sauf que faire ainsi pose plusieurs problèmes. Premièrement, cela complexifie le travail du séquenceur, qui doit enchainer deux micro-opérations. Ensuite, cela demande de câbler un chemin reliant la sortie de l'ALU entière à l'unité mémoire. Rien d'impossible, on verra même cela en détail à la fin du chapitre. Mais cette connexion est souvent difficile à câbler.
En ajoutant une AGU, les deux défauts disparaissent. La liaison entre ALU et unité mémoire disparait, l'unité mémoire n'est reliée qu'aux registres. Le séquenceur est aussi simplifié, vu que les accès mémoire ne font plus qu'une seule micro-opération, il n'y a plus besoin d'utiliser une micro-opération pour les calculs d'adresse. Le câblage du processeur est simplifié, au prix d'un cout modéré en transistors. On peut même obtenir nune économie, les simplifications du séquenceur sur-compensant le cout en transistors de l'AGU.
Parlons rapidement de l'implémentation du mode d'adressage "Base + Indice + Décalage", qui additionne une adresse de base, un indice (mis à l'échelle) et un décalage (une constante immédiate). Il demande d'additionner trois opérandes, chose que l'ALU entière ne sait pas faire (sauf exceptions). En théorie, une AGU peut réaliser ce calcul sans cout, en utilisant un additionneur ''carry save'', capable d'additionner trois opérandes à la fois. L'AGU est alors implémentée avec un circuit décaleur, pour décaler l'indice, et un additionneur trois-opérandes. Le résultat est que l'on peut faire les calculs d'adresse en un seul cycle d'horloge, pour un cout en hardware modéré.
[[File:Unité de calcul d'adresse.png|centre|vignette|upright=2|Unité de calcul d'adresse]]
Chose étrange, ce n'est pas ce qui était fait sur les premiers processeurs Intel. Sur ces CPUs, le calcul de l'adresse se faisait en deux cycles d'horloge. Lors du premier cycle d'horloge, l'ALU entière faisait le calcul d'adresse "Base + Indice". Lors du second cycle, le résultat de l'addition précédente est additionné avec le décalage, dans l'AGU. Pour cela, l'AGU était reliée à l'ALU entière et à l'unité de contrôle. Difficile de comprendre cette décision, l'implémentation ne profitant que partiellement des avantages mentionnés plus haut pour ce mode d'adressage.
===Le rafraichissement mémoire optimisé et le contrôleur mémoire intégré===
Depuis les années 80, les processeurs sont souvent combinés avec une mémoire principale de type DRAM. De telles mémoires doivent être rafraichies régulièrement pour ne pas perdre de données. Le rafraichissement se fait généralement adresse par adresse, ou ligne par ligne (les lignes sont des super-bytes internes à la DRAM). Le rafraichissement est en théorie géré par le contrôleur mémoire installé sur la carte mère. Mais au tout début de l'informatique, du temps des processeurs 8 bits, le rafraichissement mémoire était géré directement par le processeur.
Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le processeur 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''. Et tout cela était intégré à l'unité d'accès mémoire.
Depuis les années 2000, les processeurs modernes ont un contrôleur mémoire DRAM intégré directement dans le processeur. Ce qui fait qu'ils gèrent non seulement le rafraichissement, mais aussi d'autres fonctions bien pus complexes.
===L'interface de l'unité mémoire===
Vu de l'extérieur, l'unité mémoire ressemble à n'importe quel circuit électronique, avec des entrées et des sorties. L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie. Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. Si l'unité mémoire incorpore une AGU, on envoie aussi les indices et autres données sur le port d'entrée. Le port de sortie est utilisé pour récupérer le résultat des lectures. Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues.
L'unité mémoire est connectée au reste du processeur grâce à un réseau d'interconnexion qu'on étudiera plus loin. Les connexions principales sont celles avec les registres : les adresses à lire/écrire sont souvent lues depuis les registres, les lectures copient une donnée dans un registre. Il peut y avoir une connexion avec l'unité de calcul pour les opérations ''load-up'', ou pour le calcul d'adresse, mais c'est secondaire.
[[File:Unité d'accès mémoire LOAD-STORE.png|centre|vignette|upright=2|Unité d'accès mémoire LOAD-STORE.]]
===Un exemple : l'unité mémoire du 8086 et ses dérivés===
Après avoir vu la théorie, voyons un exemple du monde réel. Nous allons étudier l'exemple du processeur Intel 8086 et de son dérivé, le 80186. Ces processeurs avaient une particularité : leur unité mémoire était fortement séparée du reste du processeur. Le processeur était séparé en une ''Bus Interface Unit'' et une ''Execution Unit''. L'''Execution Unit'' a un nom trompeur : c'est l'ensemble du processeur, unité mémoire exclue. La ''Bus Interface Unit'' est l'unité mémoire. Elle se charge à la fois du chargement des données et des instructions.
Cependant, nous n'allons pouvoir détailler cette unité dans le détail, car nous n'avons pas encore vu la mémoire virtuelle, ni le préchargement. Mais cela ne nous gênera pas, vous aurez simplement quelques détails passés sous silence pour ce qui est de l'unité de calcul. Toujours est-il que voici à quoi ressemble l'intérieur d'un 80186. Elle contient :
* des registres d'adresse utilisés pour ses fonctionnalités de segmentation ;
* une unité de calcul d'adresse utilisée pour les calculs d'adresse ;
* une interface avec le bus mémoire, qu'on détaillera plus bas ;
* une mémoire FIFO où les instructions sont chargées en avance, appelée la ''Prefetch Input Queue''.
[[File:80186 arch.png|centre|vignette|upright=2.5|Microarchitecture du 80186.]]
Nous détaillerons la ''Prefetch Input Queue'' dans le chapitre sur le préchargement. Les registres et l'unité de calcul seront détaillés dans le chapitre sur la mémoire virtuelle. Il est intéressant de regarder l'interface avec le bus mémoire. Cette interface contient les registres d’interfaçage, ainsi que des circuits annexes.
Pour comprendre ce qu'elle fait, il faut savoir que le processeur communique avec la mémoire avec une séquence très précise, qui dure exactement quatre cycles. Les quatre cycles sont appelés des ''T-states'' et ils sont numérotés de 1 à 4 : T1, T2, T3 et T4.
Pour une lecture, l'adresse est envoyée lors du premier cycle, elle est disponible sur le bus au troisième cycle. Les cycles 1 et 3 sont inutilisés, le processeur se déconnecte du bus lors de ces cycles.
{|class="wikitable"
|-
! T1 !! T2 !! T3 !! T4
|-
| Adresse || || Donnée lue ||
|}
Pour une lecture, l'adresse est envoyée lors du premier cycle, la donnée est envoyée sur le bus lors des trois cycles suivants.
{|class="wikitable"
! T1 !! T2 !! T3 !! T4
|-
| Adresse || colspan="3 | Donnée à écrire
|}
Les ''timings'' vus plus haut ne sont effectivement observés que si la mémoire est assez rapide. Mais le 8086 pouvait être combiné avec des mémoires RAM assez diverses, certaines plus lentes que d'autres. Si la mémoire est trop lente, elle peut prendre plusieurs cycles avant de renvoyer la donnée lue ou d'accepter la donnée à écrire. Pour gérer la situation, le processeur pouvait attendre que la mémoire réponde, avec des ''wait states''.
Pour cela, la mémoire prévenait quand la donnée lue était disponible, ou quand une écriture était terminée. Le processeur avait une entrée READY qui indiquait que la mémoire était disponible ou qu'elle avait terminé son travail. Lors d'une lecture, la mémoire mettait cette entrée READY à 1 quand la donnée lue était disponible sur le bus mémoire. Idem pour une écriture. L'implémentation dans l'unité mémoire est très simple : le processeur restait dans le cycle T3 en attendant que l'entrée READY soit à 1. Lors d'une écriture, cela maintenait à donnée à écrire sur le bus mémoire. Pour une lecture, cela permettait de laisser le registre d’interfaçage des données connecté au bus mémoire, en attendant la donnée lue.
Nous venons de voir que le processeur pouvait rester dans l'état T3 si le besoin s'en fait sentir. Mais cette astuce est utilisée dans d'autres situations. Par exemple, le processeur dispose d'une instruction HALT, qui le déconnecte totalement du bus. Le processeur reste déconnecté tant qu'il ne reçoit pas un RESET ou une interruption. Il s'agit d'une forme limitée de mise en veille, qui n'utilisait pas de méthodes pour économiser de l'énergie. L'instruction HALT était implémentée en restant en permanence dans l'état T2. Le processeur attendait d'avoir terminé toute instruction précédente, puis lançait une pseudo-lecture et restait en état T1 en attendant une interruption ou un RESET.
Le registre d’interfaçage pour les adresses est connecté sur le bus mémoire lors du premier cycle, celui pour les données l'est à des cycles différents suivant l'opération demandée. La connexion/déconnexion des registres d’interfaçage se fait en utilisant des circuits trois-états. Il y a une couche de circuits trois-états entre le registre d’interfaçage et le bus mémoire, qui permet de connecter/déconnecter les registres du bus mémoire, mais aussi d'imposer le sens de transfert suivant qu'on fasse une lecture ou une écriture.
[[File:Circuit d'interfacage mémoire du 8086.png|centre|vignette|upright=2|Circuit d’interfaçage mémoire du 8086.]]
Le circuit en question est un circuit séquentiel assez simple, une petite "machine à état" comme on le dit en termes techniques. Le circuit séquentiel commande les circuits trois-états. Une implémentation naïve serait d'utiliser un compteur pouvant compter de 0 à 3, avec des comparateurs. Il faut, en tout :
* Un comparateur qui détermine si le compteur est au cycle 1. Si c'est le cas, il connecte le registre d’interfaçage d'adresse sur le bus mémoire.
* Un autre comparateur détermine si une lecture est en cours et que l'on est dans le troisième cycle. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode lecture.
* Un autre comparateur détermine si une écriture est en cours et que l'on est dans les cycles adéquats. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode écriture.
Afin de simplifier fortement le circuit, le compteur utilisé utilise la représentation ''one hot''. Pour rappel, cela veut dire qu'il y a une bascule pour chaque valeur que peut prendre le compteur. Le compteur ''one hot'' est composé de quatre bascules. La première bascule est à 1 pour le cycle T1, la seconde est à 1 pour le cycle T2, la troisième est à 1 pour le cycle T3, la quatrième l'est pour le cycle T4. Ainsi, on peut se passer de certains comparateurs. Par exemple, le comparateur pour l'adresse disparait : la bascule commande directement les circuits trois-états. Les deux autres comparateurs se transforment des circuits à respectivement une ou quatre portes logiques.
Incrémenter le compteur se fait cependant sous certaines conditions. La machine à état contient des circuits qui décident quand incrémenter le compteur. Pour cela, il y a un circuit qui détermine quand on peut passer du cycle T1 au cycle T2, un autre pour le passage de T2 à T3, etc. Les circuits en question sont un paquet de portes logiques assez simple. Ils reçoivent des signaux de commande provenant du séquenceur, ainsi que des entrées comme l'entrée READY pour les ''wait states''.
[[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|centre|vignette|upright=2|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]]
Maintenant, parlons des problématiques liées au calcul d'adresse. Tous les accès mémoire demandent de faire un calcul d'adresse, en raison de l'usage de la fonctionnalité de segmentation. Et ce calcul d'adresse prend deux cycles. L'adresse est calculée, puis que l'accès mémoire est réalisé. En tout, cela fait 6 cycles : 2 pour le calcul d'adresse, puis les cycles T1, T2, T3 et T4.
Une optimisation importante est que deux accès mémoire peuvent se recouvrir. A savoir que pendant qu'un accès mémoire a lieu sur le bus mémoire, on calcule l'adresse de l'accès du suivant. Les deux ont lieu dans des circuits séparés, ce qui fait que c'est possible. Pour donner un exemple, on peut calculer l'adresse de la prochaine instruction, pendant que l'instruction mémoire en cours est exécutée. Le séquenceur du processeur est configuré pour démarrer le calcul d'adresse au bon moment. Il attend que le compteur soit dans l'état T3 et T4.
{|class="wikitable"
|-
! Unité de calcul
| class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || || || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || || || ||
|-
! Bus mémoire
| || || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2
|}
: La machine à état est décrite en détail dans cet article de blog : [https://www.righto.com/2024/04/intel-8088-bus-state-machine.html Talking to memory: Inside the Intel 8088 processor's bus interface state machine ].
==Le chemin de données et son réseau d'interconnexions==
Nous venons de voir que le chemin de données contient une unité de calcul (parfois plusieurs), des registres isolés, un banc de registre, une unité mémoire. Le tout est chapeauté par une unité de contrôle qui commande le chemin de données, qui fera l'objet des prochains chapitres. Mais il faut maintenant relier registres, ALU et unité mémoire pour que l'ensemble fonctionne. Pour cela, diverses interconnexions internes au processeur se chargent de relier le tout.
Sur les anciens processeurs, les interconnexions sont assez simples et se résument à un ou deux '''bus internes au processeur''', reliés au bus mémoire. C'était la norme sur des architectures assez ancienne, qu'on n'a pas encore vu à ce point du cours, appelées les architectures à accumulateur et à pile. Mais ce n'est plus la solution utilisée actuellement. De nos jours, le réseaux d'interconnexion intra-processeur est un ensemble de connexions point à point entre ALU/registres/unité mémoire. Et paradoxalement, cela rend plus facile de comprendre ce réseau d'interconnexion.
===Introduction propédeutique : l'implémentation des modes d'adressage principaux===
L'organisation interne du processeur dépend fortement des modes d'adressage supportés. Pour simplifier les explications, nous allons séparer les modes d'adressage qui gèrent les pointeurs et les autres. Suivant que le processeur supporte les pointeurs ou non, l'organisation des bus interne est légèrement différente. La différence se voit sur les connexions avec le bus d'adresse et de données.
Tout processeur gère au minimum le '''mode d'adressage absolu''', où l'adresse est intégrée à l'instruction. Le séquenceur extrait l'adresse mémoire de l'instruction, et l'envoie sur le bus d'adresse. Pour cela, le séquenceur est relié au bus d'adresse, le chemin de donnée est relié au bus de données. Le chemin de donnée n'est pas connecté au bus d'adresse, il n'y a pas d'autres connexions.
[[File:Chemin de données sans support des pointeurs.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs]]
Le '''support des pointeurs''' demande d'intégrer des modes d'adressage dédiés : l'adressage indirect à registre, l'adresse base + indice, et les autres. Les pointeurs sont stockés dans le banc de registre et sont modifiés par l'unité de calcul. Pour supporter les pointeurs, le chemin de données est connecté sur le bus d'adresse avec le séquenceur. Suivant le mode d'adressage, le bus d'adresse est relié soit au chemin de données, soit au séquenceur.
[[File:Chemin de données avec support des pointeurs.png|centre|vignette|upright=2|Chemin de données avec support des pointeurs]]
Pour terminer, il faut parler des instructions de '''copie mémoire vers mémoire''', qui copient une donnée d'une adresse mémoire vers une autre. Elles ne se passent pas vraiment dans le chemin de données, mais se passent purement au niveau des registres d’interfaçage. L'usage d'un registre d’interfaçage unique permet d'implémenter ces instructions très facilement. Elle se fait en deux étapes : on copie la donnée dans le registre d’interfaçage, on l'écrit en mémoire RAM. L'adresse envoyée sur le bus d'adresse n'est pas la même lors des deux étapes.
===Le banc de registre est multi-port, pour gérer nativement les opérations dyadiques===
Les architectures RISC et CISC incorporent un banc de registre, qui est connecté aux unités de calcul et au bus mémoire. Et ce banc de registre peut être mono-port ou multiport. S'il a existé d'anciennes architectures utilisant un banc de registre mono-port, elles sont actuellement obsolètes. Nous les aborderons dans un chapitre dédié aux architectures dites canoniques, mais nous pouvons les laisser de côté pour le moment. De nos jours, tous les processeurs utilisent un banc de registre multi-port.
[[File:Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres).png|centre|vignette|upright=2|Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres)]]
Le banc de registre multiport est optimisé pour les opérations dyadiques. Il dispose précisément de deux ports de lecture et d'un port d'écriture pour l'écriture. Un port de lecture par opérande et le port d'écriture pour enregistrer le résultat. En clair, le processeur peut lire deux opérandes et écrire un résultat en un seul cycle d'horloge. L'avantage est que les opérations simples ne nécessitent qu'une micro-opération, pas plus.
[[File:ALU data paths.svg|centre|vignette|upright=1.5|Processeur LOAD-STORE avec un banc de registre multiport, avec les trois ports mis en évidence.]]
===Une architecture LOAD-STORE basique, avec adressage absolu===
Voyons maintenant comment l'implémentation d'une architecture RISC très simple, qui ne supporte pas les adressages pour les pointeurs, juste les adressages inhérent (à registres) et absolu (par adresse mémoire). Les instructions LOAD et STORE utilisent l'adressage absolu, géré par le séquenceur, reste à gérer l'échange entre banc de registres et bus de données. Une lecture LOAD relie le bus de données au port d'écriture du banc de registres, alors que l'écriture relie le bus au port de lecture du banc de registre. Pour cela, il faut ajouter des multiplexeurs sur les chemins existants, comme illustré par le schéma ci-dessous.
[[File:Bus interne au processeur sur archi LOAD STORE avec banc de registres multiport.png|centre|vignette|upright=2|Organisation interne d'une architecture LOAD STORE avec banc de registres multiport. Nous n'avons pas représenté les signaux de commandes envoyés par le séquenceur au chemin de données.]]
Ajoutons ensuite les instructions de copie entre registres, souvent appelées instruction COPY ou MOV. Elles existent sur la plupart des architectures LOAD-STORE. Une première solution boucle l'entrée du banc de registres sur son entrée, ce qui ne sert que pour les copies de registres.
[[File:Chemin de données d'une architecture LOAD-STORE.png|centre|vignette|upright=2|Chemin de données d'une architecture LOAD-STORE]]
Mais il existe une seconde solution, qui ne demande pas de modifier le chemin de données. Il est possible de faire passer les copies de données entre registres par l'ALU. Lors de ces copies, l'ALU une opération ''Pass through'', à savoir qu'elle recopie une des opérandes sur sa sortie. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, dans le sens où cela permet d'économiser des multiplexeurs. Mais nous verrons cela sous peu. D'ailleurs, dans la suite du chapitre, nous allons partir du principe que les copies entre registres passent par l'ALU, afin de simplifier les schémas.
===L'ajout des modes d'adressage indirects à registre pour les pointeurs===
Passons maintenant à l'implémentation des modes d'adressages pour les pointeurs. Avec eux, l'adresse mémoire à lire/écrire n'est pas intégrée dans une instruction, mais est soit dans un registre, soit calculée par l'ALU.
Le premier mode d'adressage de ce type est le mode d'adressage indirect à registre, où l'adresse à lire/écrire est dans un registre. L'implémenter demande donc de connecter la sortie du banc de registres au bus d'adresse. Il suffit d'ajouter un MUX en sortie d'un port de lecture.
[[File:Chemin de données à trois bus.png|centre|vignette|upright=2|Chemin de données à trois bus.]]
Le mode d'adressage base + indice est un mode d'adressage où l'adresse à lire/écrire est calculée à partir d'une adresse et d'un indice, tous deux présents dans un registre. Le calcul de l'adresse peut être fait soit dans l'ALU entière, soit dans une AGU dédiée. S'il est réalisé dans l'ALU entière, on doit connecter la sortie de l'unité de calcul au bus d'adresse. Si l'unité mémoire intègre une AGU, il suffit de relier l'unité mémoire aux deux ports de lecture du banc de registre. Le schéma du dessous montre ce qu'il en est avec un calcul d'adresse fait dans l'ALU entière.
[[File:Bus avec adressage base+index.png|centre|vignette|upright=2|Bus avec adressage base+index]]
Le chemin de données précédent gère aussi le mode d'adressage indirect avec pré-décrément. Pour rappel, ce mode d'adressage est une variante du mode d'adressage indirect, qui utilise une pointeur/adresse stocké dans un registre. La différence est que ce pointeur est décrémenté avant d'être envoyé sur le bus d'adresse. L'implémentation matérielle est la même que pour le mode Base + Indice : l'adresse est lue depuis les registres, décrémentée dans l'ALU, et envoyée sur le bus d'adresse.
Le schéma précédent montre que le bus d'adresse est connecté à un MUX avant l'ALU et un autre MUX après. Mais il est possible de se passer du premier MUX, utilisé pour le mode d'adressage indirect à registre. La condition est que l'ALU supporte l'opération ''pass through'', un NOP, qui recopie une opérande sur sa sortie. L'ALU fera une opération NOP pour le mode d'adressage indirect à registre, un calcul d'adresse pour le mode d'adressage base + indice. Par contre, faire ainsi rendra l'adressage indirect légèrement plus lent, vu que le temps de passage dans l'ALU sera compté.
[[File:Bus avec adressage indirect.png|centre|vignette|upright=2|Bus avec adressages pour les pointeurs, simplifié.]]
Dans ce qui va suivre, nous allons partir du principe que le processeur est implémenté en suivant le schéma précédent, afin d'avoir des schéma plus lisibles.
===L'adressage immédiat et les modes d'adressages exotiques===
Passons maintenant au mode d’adressage immédiat, qui permet de préciser une constante dans une instruction directement. La constante est extraite de l'instruction par le séquenceur, puis insérée au bon endroit dans le chemin de données. Pour les opérations arithmétiques/logiques/branchements, il faut insérer la constante extraite sur l'entrée de l'ALU. Sur certains processeurs, la constante peut être négative et doit alors subir une extension de signe dans un circuit spécialisé.
[[File:Chemin de données - Adressage immédiat avec extension de signe.png|centre|vignette|upright=2|Chemin de données - Adressage immédiat avec extension de signe.]]
L'implémentation précédente gère aussi les modes d'adressage base + décalage et absolu indexé. Pour rappel, le premier ajoute une constante à une adresse prise dans les registres, le second prend une adresse constante et lui ajoute un indice pris dans les registres. Dans les deux cas, on lit un registre, extrait une constante/adresse de l’instruction, additionne les deux dans l'ALU, avant d'envoyer le résultat sur le bus d'adresse. La seule difficulté est de désactiver l'extension de signe pour les adresses.
Le mode d'adressage absolu peut être traité de la même manière, si l'ALU est capable de faire des NOPs. L'adresse est insérée au même endroit que pour le mode d'adressage immédiat, parcours l'unité de calcul inchangée parce que NOP, et termine sur le bus d'adresse.
[[File:Chemin de données avec une ALU capable de faire des NOP.png|centre|vignette|upright=2|Chemin de données avec adressage immédiat étendu pour gérer des adresses.]]
Passons maintenant au cas particulier d'une instruction MOV qui copie une constante dans un registre. Il n'y a rien à faire si l'unité de calcul est capable d'effectuer une opération NOP/''pass through''. Pour charger une constante dans un registre, l'ALU est configurée pour faire un NOP, la constante traverse l'ALU et se retrouve dans les registres. Si l'ALU ne gère pas les NOP, la constante doit être envoyée sur l'entrée d'écriture du banc de registres, à travers un MUX dédié.
[[File:Implémentation de l'adressage immédiat dans le chemin de données.png|centre|vignette|upright=2|Implémentation de l'adressage immédiat dans le chemin de données]]
===Les architectures CISC : les opérations ''load-op''===
Tout ce qu'on a vu précédemment porte sur les processeurs de type LOAD-STORE, souvent confondus avec les processeurs de type RISC, où les accès mémoire sont séparés des instructions utilisant l'ALU. Il est maintenant temps de voir les processeurs CISC, qui gèrent des instructions ''load-op'', qui peuvent lire une opérande depuis la mémoire.
L'implémentation des opérations ''load-op'' relie le bus de donnée directement sur une entrée de l'unité de calcul, en utilisant encore une fois un multiplexeur. L'implémentation parait simple, mais c'est parce que toute la complexité est déportée dans le séquenceur. C'est lui qui se charge de détecter quand la lecture de l'opérande est terminée, quand l'opérande est disponible.
Les instructions ''load-op'' s'exécutent en plusieurs étapes, en plusieurs micro-opérations. Il y a typiquement une étape pour l'opérande à lire en mémoire et une étape de calcul. L'usage d'un registre d’interfaçage permet d'implémenter les instructions ''load-op'' très facilement. Une opération ''load-op'' charge l'opérande en mémoire dans un registre d’interfaçage, puis relier ce registre d’interfaçage sur une des entrées de l'ALU. Un simple multiplexeur suffit pour implémenter le tout, en plus des modifications adéquates du séquenceur.
[[File:Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire.png|centre|vignette|upright=2|Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire]]
Supporter les instructions multi-accès (qui font plusieurs accès mémoire) ne modifie pas fondamentalement le réseau d'interconnexion, ni le chemin de données La raison est que supporter les instructions multi-accès se fait au niveau du séquenceur. En réalité, les accès mémoire se font en série, l'un après l'autre, sous la commande du séquenceur qui émet plusieurs micro-opérations mémoire consécutives. Les données lues sont placées dans des registres d’interactivement mémoire, ce qui demande d'ajouter des registres d’interfaçage mémoire en plus.
==Annexe : le cas particulier du pointeur de pile==
Le pointeur de pile est un registre un peu particulier. Il peut être placé dans le chemin de données ou dans le séquenceur, voire dans l'unité de chargement, tout dépend du processeur. Tout dépend de si le pointeur de pile gère une pile d'adresses de retour ou une pile d'appel.
===Le pointeur de pile non-adressable explicitement===
Avec une pile d'adresse de retour, le pointeur de pile n'est pas adressable explicitement, il est juste adressé implicitement par des instructions d'appel de fonction CALL et des instructions de retour de fonction RET. Le pointeur de pile est alors juste incrémenté ou décrémenté par un pas constant, il ne subit pas d'autres opérations, son adressage est implicite. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Il n'y a pas besoin de le relier au chemin de données, vu qu'il n'échange pas de données avec les autres registres. Il y a alors plusieurs solutions, mais la plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Quelques processeurs simples disposent d'une pile d'appel très limitée, où le pointeur de pile n'est pas adressable explicitement. Il est adressé implicitement par les instruction CALL, RET, mais aussi PUSH et POP, mais aucune autre instruction ne permet cela. Là encore, le pointeur de pile ne communique pas avec les autres registres. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Là encore, le plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Dans les deux cas, le pointeur de pile est placé dans l'unité de contrôle, le séquenceur, et est associé à un incrémenteur dédié. Il se trouve que cet incrémenteur est souvent partagé avec le ''program counter''. En effet, les deux sont des adresses mémoire, qui sont incrémentées et décrémentées par pas constants, ne subissent pas d'autres opérations (si ce n'est des branchements, mais passons). Les ressemblances sont suffisantes pour fusionner les deux circuits. Ils peuvent donc avoir un '''incrémenteur partagé'''.
L'incrémenteur en question est donc partagé entre pointeur de pile, ''program counter'' et quelques autres registres similaires. Par exemple, le Z80 intégrait un registre pour le rafraichissement mémoire, qui était réalisé par le CPU à l'époque. Ce registre contenait la prochaine adresse mémoire à rafraichir, et était incrémenté à chaque rafraichissement d'une adresse. Et il était lui aussi intégré au séquenceur et incrémenté par l'incrémenteur partagé.
[[File:Organisation interne d'une architecture à pile.png|centre|vignette|upright=2|Organisation interne d'une architecture à pile]]
===Le pointeur de pile adressable explicitement===
Maintenant, étudions le cas d'une pile d'appel, précisément d'une pile d'appel avec des cadres de pile de taille variable. Sous ces conditions, le pointeur de pile est un registre adressable, avec un nom/numéro de registre dédié. Tel est par exemple le cas des processeurs x86 avec le registre ESP (''Extended Stack Pointer''). 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 calculs d'adresse, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres.
Dans ce cas, la meilleure solution est de placer le pointeur de pile dans le banc de registre généraux, avec les autres registres entiers. En faisant cela, la manipulation du pointeur de pile est faite par l'unité de calcul entière, pas besoin d'utiliser un incrémenteur dédiée. Il a existé des processeurs qui mettaient le pointeur de pile dans le banc de registre, mais l'incrémentaient avec un incrémenteur dédié, mais nous les verrons dans le chapitre sur les architectures à accumulateur. La raison est que sur les processeurs concernés, les adresses ne faisaient pas la même taille que les données : c'était des processeurs 8 bits, qui géraient des adresses de 16 bits.
==Annexe : l'implémentation du système d'''aliasing'' des registres des CPU x86==
Il y a quelques chapitres, nous avions parlé du système d'''aliasing'' des registres des CPU x86. Pour rappel, il permet de donner plusieurs noms de registre pour un même registre. Plus précisément, pour un registre 64 bits, le registre complet aura un nom de registre, les 32 bits de poids faible auront leur nom de registre dédié, idem pour les 16 bits de poids faible, etc. Il est possible de faire des calculs sur ces moitiés/quarts/huitièmes de registres sans problème.
===L'''aliasing'' du 8086, pour les registres 16 bits===
[[File:Register 8086.PNG|vignette|Register 8086]]
L'implémentation de l'''aliasing'' est apparue sur les premiers CPU Intel 16 bits, notamment le 8086. En tout, ils avaient quatre registres généraux 16 bits : AX, BX, CX et DX. Ces quatre registres 16 bits étaient coupés en deux octets, chacun adressable. Par exemple, le registre AX était coupé en deux octets nommés AH et AL, chacun ayant son propre nom/numéro de registre. Les instructions d'addition/soustraction pouvaient manipuler le registre AL, ou le registre AH, ce qui modifiait les 8 bits de poids faible ou fort selon le registre choisit.
Le banc de registre ne gére que 4 registres de 16 bits, à savoir AX, BX, CX et DX. Lors d'une lecture d'un registre 8 bits, le registre 16 bit entier est lu depuis le banc de registre, mais les bits inutiles sont ignorés. Par contre, l'écriture peut se faire soit avec 16 bits d'un coup, soit pour seulement un octet. Le port d'écriture du banc de registre peut être configuré de manière à autoriser l'écriture soit sur les 16 bits du registre, soit seulement sur les 8 bits de poids faible, soit écrire dans les 8 bits de poids fort.
[[File:Port d'écriture du banc de registre du 8086.png|centre|vignette|upright=2.5|Port d'écriture du banc de registre du 8086]]
Une opération sur un registre 8 bits se passe comme suit. Premièrement, on lit le registre 16 bits complet depuis le banc de registre. Si l'on a sélectionné l'octet de poids faible, il ne se passe rien de particulier, l'opérande 16 bits est envoyée directement à l'ALU. Mais si on a sélectionné l'octet de poids fort, la valeur lue est décalée de 7 rangs pour atterrir dans les 8 octets de poids faible. Ensuite, l'unité de calcul fait un calcul avec cet opérande, un calcul 16 bits tout ce qu'il y a de plus classique. Troisièmement, le résultat est enregistré dans le banc de registre, en le configurant convenablement. La configuration précise s'il faut enregistrer le résultat dans un registre 16 bits, soit seulement dans l'octet de poids faible/fort.
Afin de simplifier le câblage, les 16 bits des registres AX/BX/CX/DX sont entrelacés d'une manière un peu particulière. Intuitivement, on s'attend à ce que les bits soient physiquement dans le même ordre que dans le registre : le bit 0 est placé à côté du bit 1, suivi par le bit 2, etc. Mais à la place, l'octet de poids fort et de poids faible sont mélangés. Deux bits consécutifs appartiennent à deux octets différents. Le tout est décrit dans le tableau ci-dessous.
{|class="wikitable"
|-
! Registre 16 bits normal
| class="f_bleu" | 15
| class="f_bleu" | 14
| class="f_bleu" | 13
| class="f_bleu" | 12
| class="f_bleu" | 11
| class="f_bleu" | 10
| class="f_bleu" | 9
| class="f_bleu" | 8
| class="f_rouge" | 7
| class="f_rouge" | 6
| class="f_rouge" | 5
| class="f_rouge" | 4
| class="f_rouge" | 3
| class="f_rouge" | 2
| class="f_rouge" | 1
| class="f_rouge" | 0
|-
! Registre 16 bits du 8086
| class="f_bleu" | 15
| class="f_rouge" | 7
| class="f_bleu" | 14
| class="f_rouge" | 6
| class="f_bleu" | 13
| class="f_rouge" | 5
| class="f_bleu" | 12
| class="f_rouge" | 4
| class="f_bleu" | 11
| class="f_rouge" | 3
| class="f_bleu" | 10
| class="f_rouge" | 2
| class="f_bleu" | 9
| class="f_rouge" | 1
| class="f_bleu" | 8
| class="f_rouge" | 0
|}
En faisant cela, le décaleur en entrée de l'ALU est bien plus simple. Il y a 8 multiplexeurs, mais le câblage est bien plus simple. Par contre, en sortie de l'ALU, il faut remettre les bits du résultat dans l'ordre adéquat, celui du registre 8086. Pour cela, les interconnexions sur le port d'écriture sont conçues pour. Il faut juste mettre les fils de sortie de l'ALU sur la bonne entrée, par besoin de multiplexeurs.
===L'''aliasing'' sur les processeurs x86 32/64 bits===
Les processeurs x86 32 et 64 bits ont un système d'''aliasing'' qui complète le système précédent. Les processeurs 32 bits étendent les registres 16 bits existants à 32 bits. Pour ce faire, le registre 32 bit a un nouveau nom de registre, distincts du nom de registre utilisé pour l'ancien registre 16 bits. Il est possible d'adresser les 16 bits de poids faible de ce registre, avec le même nom de registre que celui utilisé pour le registre 16 sur les processeurs d'avant. Même chose avec les processeurs 64, avec l'ajout d'un nouveau nom de registre pour adresser un registre de 64 bit complet.
En soit, implémenter ce système n'est pas compliqué. Prenons le cas du registre RAX (64 bits), et de ses subdivisions nommées EAX (32 bits), AX (16 bits). À l'intérieur du banc de registre, il n'y a que le registre RAX. Le banc de registre ne comprend qu'un seul nom de registre : RAX. Les subdivisions EAX et AX n'existent qu'au niveau de l'écriture dans le banc de registre. L'écriture dans le banc de registre est configurable, de manière à ne modifier que les bits adéquats. Le résultat d'un calcul de l'ALU fait 64 bits, il est envoyé sur le port d'écriture. À ce niveau, soit les 64 bits sont écrits dans le registre, soit seulement les 32/16 bits de poids faible. Le système du 8086 est préservé pour les écritures dans les 16 bits de poids faible.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les composants d'un processeur
| prevText=Les composants d'un processeur
| next=L'unité de chargement et le program counter
| nextText=L'unité de chargement et le program counter
}}
</noinclude>
beavwys4rdqryvug5zbkcysx51f2r19
772061
772060
2026-09-12T00:27:52Z
Mewtow
31375
/* L'ajout des modes d'adressage indirects à registre pour les pointeurs */
772061
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
: Rappelons que dans ce chapitre, ainsi que dans les quatre chapitres suivants, nous ne parlons que des architectures à registres généraux. Aussi, nous mettons de côté les anciens processeurs à accumulateur et les CPU 8 bits, commercialisés avant l'arrivée des registres généraux, ainsi que certaines architectures non-conventionnelles comme les DSPs, qui ont des unités de calcul d'adresse dédiées. Mais nous ne parlerons pas de celles-ci dans ce chapitre.
L'AGU est utilisée pour certains modes d'adressage, à savoir ceux qui demandent de combiner une adresse avec soit un indice, soit un décalage, plus rarement les deux. Les calculs d'adresse prennent une adresse de base, et lui ajoutent un indice ou un décalage. Il faut parfois décaler l'indice, de quelques rangs. Pas besoin de multiplications, de divisions, ou d'autres opération plus complexe : décalages et additions/soustractions suffisent. L'AGU est donc beaucoup plus simple qu'une ALU normale et se résume à un additionneur-soustracteur couplé à un décaleur. Son cout en circuits est ridicule.
Mais pourquoi ajouter une AGU, alors que l'ALU entière suffit largement pour implémenter les modes d'adressage complexes. Il est parfaitement possible de faire les calculs d'adresse dans l'ALU entière, puis d'envoyer le résultat à l'unité mémoire. Sauf que faire ainsi pose plusieurs problèmes. Premièrement, cela complexifie le travail du séquenceur, qui doit enchainer deux micro-opérations. Ensuite, cela demande de câbler un chemin reliant la sortie de l'ALU entière à l'unité mémoire. Rien d'impossible, on verra même cela en détail à la fin du chapitre. Mais cette connexion est souvent difficile à câbler.
En ajoutant une AGU, les deux défauts disparaissent. La liaison entre ALU et unité mémoire disparait, l'unité mémoire n'est reliée qu'aux registres. Le séquenceur est aussi simplifié, vu que les accès mémoire ne font plus qu'une seule micro-opération, il n'y a plus besoin d'utiliser une micro-opération pour les calculs d'adresse. Le câblage du processeur est simplifié, au prix d'un cout modéré en transistors. On peut même obtenir nune économie, les simplifications du séquenceur sur-compensant le cout en transistors de l'AGU.
Parlons rapidement de l'implémentation du mode d'adressage "Base + Indice + Décalage", qui additionne une adresse de base, un indice (mis à l'échelle) et un décalage (une constante immédiate). Il demande d'additionner trois opérandes, chose que l'ALU entière ne sait pas faire (sauf exceptions). En théorie, une AGU peut réaliser ce calcul sans cout, en utilisant un additionneur ''carry save'', capable d'additionner trois opérandes à la fois. L'AGU est alors implémentée avec un circuit décaleur, pour décaler l'indice, et un additionneur trois-opérandes. Le résultat est que l'on peut faire les calculs d'adresse en un seul cycle d'horloge, pour un cout en hardware modéré.
[[File:Unité de calcul d'adresse.png|centre|vignette|upright=2|Unité de calcul d'adresse]]
Chose étrange, ce n'est pas ce qui était fait sur les premiers processeurs Intel. Sur ces CPUs, le calcul de l'adresse se faisait en deux cycles d'horloge. Lors du premier cycle d'horloge, l'ALU entière faisait le calcul d'adresse "Base + Indice". Lors du second cycle, le résultat de l'addition précédente est additionné avec le décalage, dans l'AGU. Pour cela, l'AGU était reliée à l'ALU entière et à l'unité de contrôle. Difficile de comprendre cette décision, l'implémentation ne profitant que partiellement des avantages mentionnés plus haut pour ce mode d'adressage.
===Le rafraichissement mémoire optimisé et le contrôleur mémoire intégré===
Depuis les années 80, les processeurs sont souvent combinés avec une mémoire principale de type DRAM. De telles mémoires doivent être rafraichies régulièrement pour ne pas perdre de données. Le rafraichissement se fait généralement adresse par adresse, ou ligne par ligne (les lignes sont des super-bytes internes à la DRAM). Le rafraichissement est en théorie géré par le contrôleur mémoire installé sur la carte mère. Mais au tout début de l'informatique, du temps des processeurs 8 bits, le rafraichissement mémoire était géré directement par le processeur.
Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le processeur 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''. Et tout cela était intégré à l'unité d'accès mémoire.
Depuis les années 2000, les processeurs modernes ont un contrôleur mémoire DRAM intégré directement dans le processeur. Ce qui fait qu'ils gèrent non seulement le rafraichissement, mais aussi d'autres fonctions bien pus complexes.
===L'interface de l'unité mémoire===
Vu de l'extérieur, l'unité mémoire ressemble à n'importe quel circuit électronique, avec des entrées et des sorties. L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie. Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. Si l'unité mémoire incorpore une AGU, on envoie aussi les indices et autres données sur le port d'entrée. Le port de sortie est utilisé pour récupérer le résultat des lectures. Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues.
L'unité mémoire est connectée au reste du processeur grâce à un réseau d'interconnexion qu'on étudiera plus loin. Les connexions principales sont celles avec les registres : les adresses à lire/écrire sont souvent lues depuis les registres, les lectures copient une donnée dans un registre. Il peut y avoir une connexion avec l'unité de calcul pour les opérations ''load-up'', ou pour le calcul d'adresse, mais c'est secondaire.
[[File:Unité d'accès mémoire LOAD-STORE.png|centre|vignette|upright=2|Unité d'accès mémoire LOAD-STORE.]]
===Un exemple : l'unité mémoire du 8086 et ses dérivés===
Après avoir vu la théorie, voyons un exemple du monde réel. Nous allons étudier l'exemple du processeur Intel 8086 et de son dérivé, le 80186. Ces processeurs avaient une particularité : leur unité mémoire était fortement séparée du reste du processeur. Le processeur était séparé en une ''Bus Interface Unit'' et une ''Execution Unit''. L'''Execution Unit'' a un nom trompeur : c'est l'ensemble du processeur, unité mémoire exclue. La ''Bus Interface Unit'' est l'unité mémoire. Elle se charge à la fois du chargement des données et des instructions.
Cependant, nous n'allons pouvoir détailler cette unité dans le détail, car nous n'avons pas encore vu la mémoire virtuelle, ni le préchargement. Mais cela ne nous gênera pas, vous aurez simplement quelques détails passés sous silence pour ce qui est de l'unité de calcul. Toujours est-il que voici à quoi ressemble l'intérieur d'un 80186. Elle contient :
* des registres d'adresse utilisés pour ses fonctionnalités de segmentation ;
* une unité de calcul d'adresse utilisée pour les calculs d'adresse ;
* une interface avec le bus mémoire, qu'on détaillera plus bas ;
* une mémoire FIFO où les instructions sont chargées en avance, appelée la ''Prefetch Input Queue''.
[[File:80186 arch.png|centre|vignette|upright=2.5|Microarchitecture du 80186.]]
Nous détaillerons la ''Prefetch Input Queue'' dans le chapitre sur le préchargement. Les registres et l'unité de calcul seront détaillés dans le chapitre sur la mémoire virtuelle. Il est intéressant de regarder l'interface avec le bus mémoire. Cette interface contient les registres d’interfaçage, ainsi que des circuits annexes.
Pour comprendre ce qu'elle fait, il faut savoir que le processeur communique avec la mémoire avec une séquence très précise, qui dure exactement quatre cycles. Les quatre cycles sont appelés des ''T-states'' et ils sont numérotés de 1 à 4 : T1, T2, T3 et T4.
Pour une lecture, l'adresse est envoyée lors du premier cycle, elle est disponible sur le bus au troisième cycle. Les cycles 1 et 3 sont inutilisés, le processeur se déconnecte du bus lors de ces cycles.
{|class="wikitable"
|-
! T1 !! T2 !! T3 !! T4
|-
| Adresse || || Donnée lue ||
|}
Pour une lecture, l'adresse est envoyée lors du premier cycle, la donnée est envoyée sur le bus lors des trois cycles suivants.
{|class="wikitable"
! T1 !! T2 !! T3 !! T4
|-
| Adresse || colspan="3 | Donnée à écrire
|}
Les ''timings'' vus plus haut ne sont effectivement observés que si la mémoire est assez rapide. Mais le 8086 pouvait être combiné avec des mémoires RAM assez diverses, certaines plus lentes que d'autres. Si la mémoire est trop lente, elle peut prendre plusieurs cycles avant de renvoyer la donnée lue ou d'accepter la donnée à écrire. Pour gérer la situation, le processeur pouvait attendre que la mémoire réponde, avec des ''wait states''.
Pour cela, la mémoire prévenait quand la donnée lue était disponible, ou quand une écriture était terminée. Le processeur avait une entrée READY qui indiquait que la mémoire était disponible ou qu'elle avait terminé son travail. Lors d'une lecture, la mémoire mettait cette entrée READY à 1 quand la donnée lue était disponible sur le bus mémoire. Idem pour une écriture. L'implémentation dans l'unité mémoire est très simple : le processeur restait dans le cycle T3 en attendant que l'entrée READY soit à 1. Lors d'une écriture, cela maintenait à donnée à écrire sur le bus mémoire. Pour une lecture, cela permettait de laisser le registre d’interfaçage des données connecté au bus mémoire, en attendant la donnée lue.
Nous venons de voir que le processeur pouvait rester dans l'état T3 si le besoin s'en fait sentir. Mais cette astuce est utilisée dans d'autres situations. Par exemple, le processeur dispose d'une instruction HALT, qui le déconnecte totalement du bus. Le processeur reste déconnecté tant qu'il ne reçoit pas un RESET ou une interruption. Il s'agit d'une forme limitée de mise en veille, qui n'utilisait pas de méthodes pour économiser de l'énergie. L'instruction HALT était implémentée en restant en permanence dans l'état T2. Le processeur attendait d'avoir terminé toute instruction précédente, puis lançait une pseudo-lecture et restait en état T1 en attendant une interruption ou un RESET.
Le registre d’interfaçage pour les adresses est connecté sur le bus mémoire lors du premier cycle, celui pour les données l'est à des cycles différents suivant l'opération demandée. La connexion/déconnexion des registres d’interfaçage se fait en utilisant des circuits trois-états. Il y a une couche de circuits trois-états entre le registre d’interfaçage et le bus mémoire, qui permet de connecter/déconnecter les registres du bus mémoire, mais aussi d'imposer le sens de transfert suivant qu'on fasse une lecture ou une écriture.
[[File:Circuit d'interfacage mémoire du 8086.png|centre|vignette|upright=2|Circuit d’interfaçage mémoire du 8086.]]
Le circuit en question est un circuit séquentiel assez simple, une petite "machine à état" comme on le dit en termes techniques. Le circuit séquentiel commande les circuits trois-états. Une implémentation naïve serait d'utiliser un compteur pouvant compter de 0 à 3, avec des comparateurs. Il faut, en tout :
* Un comparateur qui détermine si le compteur est au cycle 1. Si c'est le cas, il connecte le registre d’interfaçage d'adresse sur le bus mémoire.
* Un autre comparateur détermine si une lecture est en cours et que l'on est dans le troisième cycle. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode lecture.
* Un autre comparateur détermine si une écriture est en cours et que l'on est dans les cycles adéquats. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode écriture.
Afin de simplifier fortement le circuit, le compteur utilisé utilise la représentation ''one hot''. Pour rappel, cela veut dire qu'il y a une bascule pour chaque valeur que peut prendre le compteur. Le compteur ''one hot'' est composé de quatre bascules. La première bascule est à 1 pour le cycle T1, la seconde est à 1 pour le cycle T2, la troisième est à 1 pour le cycle T3, la quatrième l'est pour le cycle T4. Ainsi, on peut se passer de certains comparateurs. Par exemple, le comparateur pour l'adresse disparait : la bascule commande directement les circuits trois-états. Les deux autres comparateurs se transforment des circuits à respectivement une ou quatre portes logiques.
Incrémenter le compteur se fait cependant sous certaines conditions. La machine à état contient des circuits qui décident quand incrémenter le compteur. Pour cela, il y a un circuit qui détermine quand on peut passer du cycle T1 au cycle T2, un autre pour le passage de T2 à T3, etc. Les circuits en question sont un paquet de portes logiques assez simple. Ils reçoivent des signaux de commande provenant du séquenceur, ainsi que des entrées comme l'entrée READY pour les ''wait states''.
[[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|centre|vignette|upright=2|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]]
Maintenant, parlons des problématiques liées au calcul d'adresse. Tous les accès mémoire demandent de faire un calcul d'adresse, en raison de l'usage de la fonctionnalité de segmentation. Et ce calcul d'adresse prend deux cycles. L'adresse est calculée, puis que l'accès mémoire est réalisé. En tout, cela fait 6 cycles : 2 pour le calcul d'adresse, puis les cycles T1, T2, T3 et T4.
Une optimisation importante est que deux accès mémoire peuvent se recouvrir. A savoir que pendant qu'un accès mémoire a lieu sur le bus mémoire, on calcule l'adresse de l'accès du suivant. Les deux ont lieu dans des circuits séparés, ce qui fait que c'est possible. Pour donner un exemple, on peut calculer l'adresse de la prochaine instruction, pendant que l'instruction mémoire en cours est exécutée. Le séquenceur du processeur est configuré pour démarrer le calcul d'adresse au bon moment. Il attend que le compteur soit dans l'état T3 et T4.
{|class="wikitable"
|-
! Unité de calcul
| class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || || || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || || || ||
|-
! Bus mémoire
| || || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2
|}
: La machine à état est décrite en détail dans cet article de blog : [https://www.righto.com/2024/04/intel-8088-bus-state-machine.html Talking to memory: Inside the Intel 8088 processor's bus interface state machine ].
==Le chemin de données et son réseau d'interconnexions==
Nous venons de voir que le chemin de données contient une unité de calcul (parfois plusieurs), des registres isolés, un banc de registre, une unité mémoire. Le tout est chapeauté par une unité de contrôle qui commande le chemin de données, qui fera l'objet des prochains chapitres. Mais il faut maintenant relier registres, ALU et unité mémoire pour que l'ensemble fonctionne. Pour cela, diverses interconnexions internes au processeur se chargent de relier le tout.
Sur les anciens processeurs, les interconnexions sont assez simples et se résument à un ou deux '''bus internes au processeur''', reliés au bus mémoire. C'était la norme sur des architectures assez ancienne, qu'on n'a pas encore vu à ce point du cours, appelées les architectures à accumulateur et à pile. Mais ce n'est plus la solution utilisée actuellement. De nos jours, le réseaux d'interconnexion intra-processeur est un ensemble de connexions point à point entre ALU/registres/unité mémoire. Et paradoxalement, cela rend plus facile de comprendre ce réseau d'interconnexion.
===Introduction propédeutique : l'implémentation des modes d'adressage principaux===
L'organisation interne du processeur dépend fortement des modes d'adressage supportés. Pour simplifier les explications, nous allons séparer les modes d'adressage qui gèrent les pointeurs et les autres. Suivant que le processeur supporte les pointeurs ou non, l'organisation des bus interne est légèrement différente. La différence se voit sur les connexions avec le bus d'adresse et de données.
Tout processeur gère au minimum le '''mode d'adressage absolu''', où l'adresse est intégrée à l'instruction. Le séquenceur extrait l'adresse mémoire de l'instruction, et l'envoie sur le bus d'adresse. Pour cela, le séquenceur est relié au bus d'adresse, le chemin de donnée est relié au bus de données. Le chemin de donnée n'est pas connecté au bus d'adresse, il n'y a pas d'autres connexions.
[[File:Chemin de données sans support des pointeurs.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs]]
Le '''support des pointeurs''' demande d'intégrer des modes d'adressage dédiés : l'adressage indirect à registre, l'adresse base + indice, et les autres. Les pointeurs sont stockés dans le banc de registre et sont modifiés par l'unité de calcul. Pour supporter les pointeurs, le chemin de données est connecté sur le bus d'adresse avec le séquenceur. Suivant le mode d'adressage, le bus d'adresse est relié soit au chemin de données, soit au séquenceur.
[[File:Chemin de données avec support des pointeurs.png|centre|vignette|upright=2|Chemin de données avec support des pointeurs]]
Pour terminer, il faut parler des instructions de '''copie mémoire vers mémoire''', qui copient une donnée d'une adresse mémoire vers une autre. Elles ne se passent pas vraiment dans le chemin de données, mais se passent purement au niveau des registres d’interfaçage. L'usage d'un registre d’interfaçage unique permet d'implémenter ces instructions très facilement. Elle se fait en deux étapes : on copie la donnée dans le registre d’interfaçage, on l'écrit en mémoire RAM. L'adresse envoyée sur le bus d'adresse n'est pas la même lors des deux étapes.
===Le banc de registre est multi-port, pour gérer nativement les opérations dyadiques===
Les architectures RISC et CISC incorporent un banc de registre, qui est connecté aux unités de calcul et au bus mémoire. Et ce banc de registre peut être mono-port ou multiport. S'il a existé d'anciennes architectures utilisant un banc de registre mono-port, elles sont actuellement obsolètes. Nous les aborderons dans un chapitre dédié aux architectures dites canoniques, mais nous pouvons les laisser de côté pour le moment. De nos jours, tous les processeurs utilisent un banc de registre multi-port.
[[File:Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres).png|centre|vignette|upright=2|Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres)]]
Le banc de registre multiport est optimisé pour les opérations dyadiques. Il dispose précisément de deux ports de lecture et d'un port d'écriture pour l'écriture. Un port de lecture par opérande et le port d'écriture pour enregistrer le résultat. En clair, le processeur peut lire deux opérandes et écrire un résultat en un seul cycle d'horloge. L'avantage est que les opérations simples ne nécessitent qu'une micro-opération, pas plus.
[[File:ALU data paths.svg|centre|vignette|upright=1.5|Processeur LOAD-STORE avec un banc de registre multiport, avec les trois ports mis en évidence.]]
===Une architecture LOAD-STORE basique, avec adressage absolu===
Voyons maintenant comment l'implémentation d'une architecture RISC très simple, qui ne supporte pas les adressages pour les pointeurs, juste les adressages inhérent (à registres) et absolu (par adresse mémoire). Les instructions LOAD et STORE utilisent l'adressage absolu, géré par le séquenceur, reste à gérer l'échange entre banc de registres et bus de données. Une lecture LOAD relie le bus de données au port d'écriture du banc de registres, alors que l'écriture relie le bus au port de lecture du banc de registre. Pour cela, il faut ajouter des multiplexeurs sur les chemins existants, comme illustré par le schéma ci-dessous.
[[File:Bus interne au processeur sur archi LOAD STORE avec banc de registres multiport.png|centre|vignette|upright=2|Organisation interne d'une architecture LOAD STORE avec banc de registres multiport. Nous n'avons pas représenté les signaux de commandes envoyés par le séquenceur au chemin de données.]]
Ajoutons ensuite les instructions de copie entre registres, souvent appelées instruction COPY ou MOV. Elles existent sur la plupart des architectures LOAD-STORE. Une première solution boucle l'entrée du banc de registres sur son entrée, ce qui ne sert que pour les copies de registres.
[[File:Chemin de données d'une architecture LOAD-STORE.png|centre|vignette|upright=2|Chemin de données d'une architecture LOAD-STORE]]
Mais il existe une seconde solution, qui ne demande pas de modifier le chemin de données. Il est possible de faire passer les copies de données entre registres par l'ALU. Lors de ces copies, l'ALU une opération ''Pass through'', à savoir qu'elle recopie une des opérandes sur sa sortie. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, dans le sens où cela permet d'économiser des multiplexeurs. Mais nous verrons cela sous peu. D'ailleurs, dans la suite du chapitre, nous allons partir du principe que les copies entre registres passent par l'ALU, afin de simplifier les schémas.
===L'ajout des modes d'adressage indirects à registre pour les pointeurs===
Passons maintenant à l'implémentation des modes d'adressages pour les pointeurs. Avec eux, l'adresse mémoire à lire/écrire n'est pas intégrée dans une instruction, mais est soit dans un registre, soit calculée par l'ALU.
Le premier mode d'adressage de ce type est le mode d'adressage indirect à registre, où l'adresse à lire/écrire est dans un registre. L'implémenter demande donc de connecter la sortie du banc de registres au bus d'adresse. Il suffit d'ajouter un MUX en sortie d'un port de lecture.
[[File:Chemin de données à trois bus.png|centre|vignette|upright=2|Chemin de données à trois bus.]]
Le mode d'adressage base + indice est un mode d'adressage où l'adresse à lire/écrire est calculée à partir d'une adresse et d'un indice, tous deux présents dans un registre. Le calcul de l'adresse peut être fait soit dans l'ALU entière, soit dans une AGU dédiée.
* Si l'unité mémoire intègre une AGU, il suffit de relier l'unité mémoire aux deux ports de lecture du banc de registre. Le schéma du dessous montre ce qu'il en est avec un calcul d'adresse fait dans l'ALU entière.
* S'il est réalisé dans l'ALU entière, on doit connecter la sortie de l'unité de calcul au bus d'adresse. Il est possible d'ajouter un registre sur cette connexion, afin de simplifier l'implémentation. L'instruction mémoire se fait alors en deux étapes : un calcul d'adresse, l'accès mémoire. L'unité mémoire lit l'adresse à lire dans le registre intercalé entre l'ALU entière et l'unité mémoire.
[[File:Bus avec adressage base+index.png|centre|vignette|upright=2|Bus avec adressage base+index]]
Le chemin de données précédent gère aussi le mode d'adressage indirect avec pré-décrément. Pour rappel, ce mode d'adressage est une variante du mode d'adressage indirect, qui utilise une pointeur/adresse stocké dans un registre. La différence est que ce pointeur est décrémenté avant d'être envoyé sur le bus d'adresse. L'implémentation matérielle est la même que pour le mode Base + Indice : l'adresse est lue depuis les registres, décrémentée dans l'ALU, et envoyée sur le bus d'adresse.
Le schéma précédent montre que le bus d'adresse est connecté à un MUX avant l'ALU et un autre MUX après. Mais il est possible de se passer du premier MUX, utilisé pour le mode d'adressage indirect à registre. La condition est que l'ALU supporte l'opération ''pass through'', un NOP, qui recopie une opérande sur sa sortie. L'ALU fera une opération NOP pour le mode d'adressage indirect à registre, un calcul d'adresse pour le mode d'adressage base + indice. Par contre, faire ainsi rendra l'adressage indirect légèrement plus lent, vu que le temps de passage dans l'ALU sera compté.
[[File:Bus avec adressage indirect.png|centre|vignette|upright=2|Bus avec adressages pour les pointeurs, simplifié.]]
Dans ce qui va suivre, nous allons partir du principe que le processeur est implémenté en suivant le schéma précédent, afin d'avoir des schéma plus lisibles.
===L'adressage immédiat et les modes d'adressages exotiques===
Passons maintenant au mode d’adressage immédiat, qui permet de préciser une constante dans une instruction directement. La constante est extraite de l'instruction par le séquenceur, puis insérée au bon endroit dans le chemin de données. Pour les opérations arithmétiques/logiques/branchements, il faut insérer la constante extraite sur l'entrée de l'ALU. Sur certains processeurs, la constante peut être négative et doit alors subir une extension de signe dans un circuit spécialisé.
[[File:Chemin de données - Adressage immédiat avec extension de signe.png|centre|vignette|upright=2|Chemin de données - Adressage immédiat avec extension de signe.]]
L'implémentation précédente gère aussi les modes d'adressage base + décalage et absolu indexé. Pour rappel, le premier ajoute une constante à une adresse prise dans les registres, le second prend une adresse constante et lui ajoute un indice pris dans les registres. Dans les deux cas, on lit un registre, extrait une constante/adresse de l’instruction, additionne les deux dans l'ALU, avant d'envoyer le résultat sur le bus d'adresse. La seule difficulté est de désactiver l'extension de signe pour les adresses.
Le mode d'adressage absolu peut être traité de la même manière, si l'ALU est capable de faire des NOPs. L'adresse est insérée au même endroit que pour le mode d'adressage immédiat, parcours l'unité de calcul inchangée parce que NOP, et termine sur le bus d'adresse.
[[File:Chemin de données avec une ALU capable de faire des NOP.png|centre|vignette|upright=2|Chemin de données avec adressage immédiat étendu pour gérer des adresses.]]
Passons maintenant au cas particulier d'une instruction MOV qui copie une constante dans un registre. Il n'y a rien à faire si l'unité de calcul est capable d'effectuer une opération NOP/''pass through''. Pour charger une constante dans un registre, l'ALU est configurée pour faire un NOP, la constante traverse l'ALU et se retrouve dans les registres. Si l'ALU ne gère pas les NOP, la constante doit être envoyée sur l'entrée d'écriture du banc de registres, à travers un MUX dédié.
[[File:Implémentation de l'adressage immédiat dans le chemin de données.png|centre|vignette|upright=2|Implémentation de l'adressage immédiat dans le chemin de données]]
===Les architectures CISC : les opérations ''load-op''===
Tout ce qu'on a vu précédemment porte sur les processeurs de type LOAD-STORE, souvent confondus avec les processeurs de type RISC, où les accès mémoire sont séparés des instructions utilisant l'ALU. Il est maintenant temps de voir les processeurs CISC, qui gèrent des instructions ''load-op'', qui peuvent lire une opérande depuis la mémoire.
L'implémentation des opérations ''load-op'' relie le bus de donnée directement sur une entrée de l'unité de calcul, en utilisant encore une fois un multiplexeur. L'implémentation parait simple, mais c'est parce que toute la complexité est déportée dans le séquenceur. C'est lui qui se charge de détecter quand la lecture de l'opérande est terminée, quand l'opérande est disponible.
Les instructions ''load-op'' s'exécutent en plusieurs étapes, en plusieurs micro-opérations. Il y a typiquement une étape pour l'opérande à lire en mémoire et une étape de calcul. L'usage d'un registre d’interfaçage permet d'implémenter les instructions ''load-op'' très facilement. Une opération ''load-op'' charge l'opérande en mémoire dans un registre d’interfaçage, puis relier ce registre d’interfaçage sur une des entrées de l'ALU. Un simple multiplexeur suffit pour implémenter le tout, en plus des modifications adéquates du séquenceur.
[[File:Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire.png|centre|vignette|upright=2|Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire]]
Supporter les instructions multi-accès (qui font plusieurs accès mémoire) ne modifie pas fondamentalement le réseau d'interconnexion, ni le chemin de données La raison est que supporter les instructions multi-accès se fait au niveau du séquenceur. En réalité, les accès mémoire se font en série, l'un après l'autre, sous la commande du séquenceur qui émet plusieurs micro-opérations mémoire consécutives. Les données lues sont placées dans des registres d’interactivement mémoire, ce qui demande d'ajouter des registres d’interfaçage mémoire en plus.
==Annexe : le cas particulier du pointeur de pile==
Le pointeur de pile est un registre un peu particulier. Il peut être placé dans le chemin de données ou dans le séquenceur, voire dans l'unité de chargement, tout dépend du processeur. Tout dépend de si le pointeur de pile gère une pile d'adresses de retour ou une pile d'appel.
===Le pointeur de pile non-adressable explicitement===
Avec une pile d'adresse de retour, le pointeur de pile n'est pas adressable explicitement, il est juste adressé implicitement par des instructions d'appel de fonction CALL et des instructions de retour de fonction RET. Le pointeur de pile est alors juste incrémenté ou décrémenté par un pas constant, il ne subit pas d'autres opérations, son adressage est implicite. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Il n'y a pas besoin de le relier au chemin de données, vu qu'il n'échange pas de données avec les autres registres. Il y a alors plusieurs solutions, mais la plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Quelques processeurs simples disposent d'une pile d'appel très limitée, où le pointeur de pile n'est pas adressable explicitement. Il est adressé implicitement par les instruction CALL, RET, mais aussi PUSH et POP, mais aucune autre instruction ne permet cela. Là encore, le pointeur de pile ne communique pas avec les autres registres. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Là encore, le plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Dans les deux cas, le pointeur de pile est placé dans l'unité de contrôle, le séquenceur, et est associé à un incrémenteur dédié. Il se trouve que cet incrémenteur est souvent partagé avec le ''program counter''. En effet, les deux sont des adresses mémoire, qui sont incrémentées et décrémentées par pas constants, ne subissent pas d'autres opérations (si ce n'est des branchements, mais passons). Les ressemblances sont suffisantes pour fusionner les deux circuits. Ils peuvent donc avoir un '''incrémenteur partagé'''.
L'incrémenteur en question est donc partagé entre pointeur de pile, ''program counter'' et quelques autres registres similaires. Par exemple, le Z80 intégrait un registre pour le rafraichissement mémoire, qui était réalisé par le CPU à l'époque. Ce registre contenait la prochaine adresse mémoire à rafraichir, et était incrémenté à chaque rafraichissement d'une adresse. Et il était lui aussi intégré au séquenceur et incrémenté par l'incrémenteur partagé.
[[File:Organisation interne d'une architecture à pile.png|centre|vignette|upright=2|Organisation interne d'une architecture à pile]]
===Le pointeur de pile adressable explicitement===
Maintenant, étudions le cas d'une pile d'appel, précisément d'une pile d'appel avec des cadres de pile de taille variable. Sous ces conditions, le pointeur de pile est un registre adressable, avec un nom/numéro de registre dédié. Tel est par exemple le cas des processeurs x86 avec le registre ESP (''Extended Stack Pointer''). 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 calculs d'adresse, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres.
Dans ce cas, la meilleure solution est de placer le pointeur de pile dans le banc de registre généraux, avec les autres registres entiers. En faisant cela, la manipulation du pointeur de pile est faite par l'unité de calcul entière, pas besoin d'utiliser un incrémenteur dédiée. Il a existé des processeurs qui mettaient le pointeur de pile dans le banc de registre, mais l'incrémentaient avec un incrémenteur dédié, mais nous les verrons dans le chapitre sur les architectures à accumulateur. La raison est que sur les processeurs concernés, les adresses ne faisaient pas la même taille que les données : c'était des processeurs 8 bits, qui géraient des adresses de 16 bits.
==Annexe : l'implémentation du système d'''aliasing'' des registres des CPU x86==
Il y a quelques chapitres, nous avions parlé du système d'''aliasing'' des registres des CPU x86. Pour rappel, il permet de donner plusieurs noms de registre pour un même registre. Plus précisément, pour un registre 64 bits, le registre complet aura un nom de registre, les 32 bits de poids faible auront leur nom de registre dédié, idem pour les 16 bits de poids faible, etc. Il est possible de faire des calculs sur ces moitiés/quarts/huitièmes de registres sans problème.
===L'''aliasing'' du 8086, pour les registres 16 bits===
[[File:Register 8086.PNG|vignette|Register 8086]]
L'implémentation de l'''aliasing'' est apparue sur les premiers CPU Intel 16 bits, notamment le 8086. En tout, ils avaient quatre registres généraux 16 bits : AX, BX, CX et DX. Ces quatre registres 16 bits étaient coupés en deux octets, chacun adressable. Par exemple, le registre AX était coupé en deux octets nommés AH et AL, chacun ayant son propre nom/numéro de registre. Les instructions d'addition/soustraction pouvaient manipuler le registre AL, ou le registre AH, ce qui modifiait les 8 bits de poids faible ou fort selon le registre choisit.
Le banc de registre ne gére que 4 registres de 16 bits, à savoir AX, BX, CX et DX. Lors d'une lecture d'un registre 8 bits, le registre 16 bit entier est lu depuis le banc de registre, mais les bits inutiles sont ignorés. Par contre, l'écriture peut se faire soit avec 16 bits d'un coup, soit pour seulement un octet. Le port d'écriture du banc de registre peut être configuré de manière à autoriser l'écriture soit sur les 16 bits du registre, soit seulement sur les 8 bits de poids faible, soit écrire dans les 8 bits de poids fort.
[[File:Port d'écriture du banc de registre du 8086.png|centre|vignette|upright=2.5|Port d'écriture du banc de registre du 8086]]
Une opération sur un registre 8 bits se passe comme suit. Premièrement, on lit le registre 16 bits complet depuis le banc de registre. Si l'on a sélectionné l'octet de poids faible, il ne se passe rien de particulier, l'opérande 16 bits est envoyée directement à l'ALU. Mais si on a sélectionné l'octet de poids fort, la valeur lue est décalée de 7 rangs pour atterrir dans les 8 octets de poids faible. Ensuite, l'unité de calcul fait un calcul avec cet opérande, un calcul 16 bits tout ce qu'il y a de plus classique. Troisièmement, le résultat est enregistré dans le banc de registre, en le configurant convenablement. La configuration précise s'il faut enregistrer le résultat dans un registre 16 bits, soit seulement dans l'octet de poids faible/fort.
Afin de simplifier le câblage, les 16 bits des registres AX/BX/CX/DX sont entrelacés d'une manière un peu particulière. Intuitivement, on s'attend à ce que les bits soient physiquement dans le même ordre que dans le registre : le bit 0 est placé à côté du bit 1, suivi par le bit 2, etc. Mais à la place, l'octet de poids fort et de poids faible sont mélangés. Deux bits consécutifs appartiennent à deux octets différents. Le tout est décrit dans le tableau ci-dessous.
{|class="wikitable"
|-
! Registre 16 bits normal
| class="f_bleu" | 15
| class="f_bleu" | 14
| class="f_bleu" | 13
| class="f_bleu" | 12
| class="f_bleu" | 11
| class="f_bleu" | 10
| class="f_bleu" | 9
| class="f_bleu" | 8
| class="f_rouge" | 7
| class="f_rouge" | 6
| class="f_rouge" | 5
| class="f_rouge" | 4
| class="f_rouge" | 3
| class="f_rouge" | 2
| class="f_rouge" | 1
| class="f_rouge" | 0
|-
! Registre 16 bits du 8086
| class="f_bleu" | 15
| class="f_rouge" | 7
| class="f_bleu" | 14
| class="f_rouge" | 6
| class="f_bleu" | 13
| class="f_rouge" | 5
| class="f_bleu" | 12
| class="f_rouge" | 4
| class="f_bleu" | 11
| class="f_rouge" | 3
| class="f_bleu" | 10
| class="f_rouge" | 2
| class="f_bleu" | 9
| class="f_rouge" | 1
| class="f_bleu" | 8
| class="f_rouge" | 0
|}
En faisant cela, le décaleur en entrée de l'ALU est bien plus simple. Il y a 8 multiplexeurs, mais le câblage est bien plus simple. Par contre, en sortie de l'ALU, il faut remettre les bits du résultat dans l'ordre adéquat, celui du registre 8086. Pour cela, les interconnexions sur le port d'écriture sont conçues pour. Il faut juste mettre les fils de sortie de l'ALU sur la bonne entrée, par besoin de multiplexeurs.
===L'''aliasing'' sur les processeurs x86 32/64 bits===
Les processeurs x86 32 et 64 bits ont un système d'''aliasing'' qui complète le système précédent. Les processeurs 32 bits étendent les registres 16 bits existants à 32 bits. Pour ce faire, le registre 32 bit a un nouveau nom de registre, distincts du nom de registre utilisé pour l'ancien registre 16 bits. Il est possible d'adresser les 16 bits de poids faible de ce registre, avec le même nom de registre que celui utilisé pour le registre 16 sur les processeurs d'avant. Même chose avec les processeurs 64, avec l'ajout d'un nouveau nom de registre pour adresser un registre de 64 bit complet.
En soit, implémenter ce système n'est pas compliqué. Prenons le cas du registre RAX (64 bits), et de ses subdivisions nommées EAX (32 bits), AX (16 bits). À l'intérieur du banc de registre, il n'y a que le registre RAX. Le banc de registre ne comprend qu'un seul nom de registre : RAX. Les subdivisions EAX et AX n'existent qu'au niveau de l'écriture dans le banc de registre. L'écriture dans le banc de registre est configurable, de manière à ne modifier que les bits adéquats. Le résultat d'un calcul de l'ALU fait 64 bits, il est envoyé sur le port d'écriture. À ce niveau, soit les 64 bits sont écrits dans le registre, soit seulement les 32/16 bits de poids faible. Le système du 8086 est préservé pour les écritures dans les 16 bits de poids faible.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les composants d'un processeur
| prevText=Les composants d'un processeur
| next=L'unité de chargement et le program counter
| nextText=L'unité de chargement et le program counter
}}
</noinclude>
rji954ijoheari5k2r00wl58oll8rm7
772062
772061
2026-09-12T00:39:45Z
Mewtow
31375
/* Une architecture LOAD-STORE basique, avec adressage absolu */
772062
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
: Rappelons que dans ce chapitre, ainsi que dans les quatre chapitres suivants, nous ne parlons que des architectures à registres généraux. Aussi, nous mettons de côté les anciens processeurs à accumulateur et les CPU 8 bits, commercialisés avant l'arrivée des registres généraux, ainsi que certaines architectures non-conventionnelles comme les DSPs, qui ont des unités de calcul d'adresse dédiées. Mais nous ne parlerons pas de celles-ci dans ce chapitre.
L'AGU est utilisée pour certains modes d'adressage, à savoir ceux qui demandent de combiner une adresse avec soit un indice, soit un décalage, plus rarement les deux. Les calculs d'adresse prennent une adresse de base, et lui ajoutent un indice ou un décalage. Il faut parfois décaler l'indice, de quelques rangs. Pas besoin de multiplications, de divisions, ou d'autres opération plus complexe : décalages et additions/soustractions suffisent. L'AGU est donc beaucoup plus simple qu'une ALU normale et se résume à un additionneur-soustracteur couplé à un décaleur. Son cout en circuits est ridicule.
Mais pourquoi ajouter une AGU, alors que l'ALU entière suffit largement pour implémenter les modes d'adressage complexes. Il est parfaitement possible de faire les calculs d'adresse dans l'ALU entière, puis d'envoyer le résultat à l'unité mémoire. Sauf que faire ainsi pose plusieurs problèmes. Premièrement, cela complexifie le travail du séquenceur, qui doit enchainer deux micro-opérations. Ensuite, cela demande de câbler un chemin reliant la sortie de l'ALU entière à l'unité mémoire. Rien d'impossible, on verra même cela en détail à la fin du chapitre. Mais cette connexion est souvent difficile à câbler.
En ajoutant une AGU, les deux défauts disparaissent. La liaison entre ALU et unité mémoire disparait, l'unité mémoire n'est reliée qu'aux registres. Le séquenceur est aussi simplifié, vu que les accès mémoire ne font plus qu'une seule micro-opération, il n'y a plus besoin d'utiliser une micro-opération pour les calculs d'adresse. Le câblage du processeur est simplifié, au prix d'un cout modéré en transistors. On peut même obtenir nune économie, les simplifications du séquenceur sur-compensant le cout en transistors de l'AGU.
Parlons rapidement de l'implémentation du mode d'adressage "Base + Indice + Décalage", qui additionne une adresse de base, un indice (mis à l'échelle) et un décalage (une constante immédiate). Il demande d'additionner trois opérandes, chose que l'ALU entière ne sait pas faire (sauf exceptions). En théorie, une AGU peut réaliser ce calcul sans cout, en utilisant un additionneur ''carry save'', capable d'additionner trois opérandes à la fois. L'AGU est alors implémentée avec un circuit décaleur, pour décaler l'indice, et un additionneur trois-opérandes. Le résultat est que l'on peut faire les calculs d'adresse en un seul cycle d'horloge, pour un cout en hardware modéré.
[[File:Unité de calcul d'adresse.png|centre|vignette|upright=2|Unité de calcul d'adresse]]
Chose étrange, ce n'est pas ce qui était fait sur les premiers processeurs Intel. Sur ces CPUs, le calcul de l'adresse se faisait en deux cycles d'horloge. Lors du premier cycle d'horloge, l'ALU entière faisait le calcul d'adresse "Base + Indice". Lors du second cycle, le résultat de l'addition précédente est additionné avec le décalage, dans l'AGU. Pour cela, l'AGU était reliée à l'ALU entière et à l'unité de contrôle. Difficile de comprendre cette décision, l'implémentation ne profitant que partiellement des avantages mentionnés plus haut pour ce mode d'adressage.
===Le rafraichissement mémoire optimisé et le contrôleur mémoire intégré===
Depuis les années 80, les processeurs sont souvent combinés avec une mémoire principale de type DRAM. De telles mémoires doivent être rafraichies régulièrement pour ne pas perdre de données. Le rafraichissement se fait généralement adresse par adresse, ou ligne par ligne (les lignes sont des super-bytes internes à la DRAM). Le rafraichissement est en théorie géré par le contrôleur mémoire installé sur la carte mère. Mais au tout début de l'informatique, du temps des processeurs 8 bits, le rafraichissement mémoire était géré directement par le processeur.
Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le processeur 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''. Et tout cela était intégré à l'unité d'accès mémoire.
Depuis les années 2000, les processeurs modernes ont un contrôleur mémoire DRAM intégré directement dans le processeur. Ce qui fait qu'ils gèrent non seulement le rafraichissement, mais aussi d'autres fonctions bien pus complexes.
===L'interface de l'unité mémoire===
Vu de l'extérieur, l'unité mémoire ressemble à n'importe quel circuit électronique, avec des entrées et des sorties. L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie. Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. Si l'unité mémoire incorpore une AGU, on envoie aussi les indices et autres données sur le port d'entrée. Le port de sortie est utilisé pour récupérer le résultat des lectures. Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues.
L'unité mémoire est connectée au reste du processeur grâce à un réseau d'interconnexion qu'on étudiera plus loin. Les connexions principales sont celles avec les registres : les adresses à lire/écrire sont souvent lues depuis les registres, les lectures copient une donnée dans un registre. Il peut y avoir une connexion avec l'unité de calcul pour les opérations ''load-up'', ou pour le calcul d'adresse, mais c'est secondaire.
[[File:Unité d'accès mémoire LOAD-STORE.png|centre|vignette|upright=2|Unité d'accès mémoire LOAD-STORE.]]
===Un exemple : l'unité mémoire du 8086 et ses dérivés===
Après avoir vu la théorie, voyons un exemple du monde réel. Nous allons étudier l'exemple du processeur Intel 8086 et de son dérivé, le 80186. Ces processeurs avaient une particularité : leur unité mémoire était fortement séparée du reste du processeur. Le processeur était séparé en une ''Bus Interface Unit'' et une ''Execution Unit''. L'''Execution Unit'' a un nom trompeur : c'est l'ensemble du processeur, unité mémoire exclue. La ''Bus Interface Unit'' est l'unité mémoire. Elle se charge à la fois du chargement des données et des instructions.
Cependant, nous n'allons pouvoir détailler cette unité dans le détail, car nous n'avons pas encore vu la mémoire virtuelle, ni le préchargement. Mais cela ne nous gênera pas, vous aurez simplement quelques détails passés sous silence pour ce qui est de l'unité de calcul. Toujours est-il que voici à quoi ressemble l'intérieur d'un 80186. Elle contient :
* des registres d'adresse utilisés pour ses fonctionnalités de segmentation ;
* une unité de calcul d'adresse utilisée pour les calculs d'adresse ;
* une interface avec le bus mémoire, qu'on détaillera plus bas ;
* une mémoire FIFO où les instructions sont chargées en avance, appelée la ''Prefetch Input Queue''.
[[File:80186 arch.png|centre|vignette|upright=2.5|Microarchitecture du 80186.]]
Nous détaillerons la ''Prefetch Input Queue'' dans le chapitre sur le préchargement. Les registres et l'unité de calcul seront détaillés dans le chapitre sur la mémoire virtuelle. Il est intéressant de regarder l'interface avec le bus mémoire. Cette interface contient les registres d’interfaçage, ainsi que des circuits annexes.
Pour comprendre ce qu'elle fait, il faut savoir que le processeur communique avec la mémoire avec une séquence très précise, qui dure exactement quatre cycles. Les quatre cycles sont appelés des ''T-states'' et ils sont numérotés de 1 à 4 : T1, T2, T3 et T4.
Pour une lecture, l'adresse est envoyée lors du premier cycle, elle est disponible sur le bus au troisième cycle. Les cycles 1 et 3 sont inutilisés, le processeur se déconnecte du bus lors de ces cycles.
{|class="wikitable"
|-
! T1 !! T2 !! T3 !! T4
|-
| Adresse || || Donnée lue ||
|}
Pour une lecture, l'adresse est envoyée lors du premier cycle, la donnée est envoyée sur le bus lors des trois cycles suivants.
{|class="wikitable"
! T1 !! T2 !! T3 !! T4
|-
| Adresse || colspan="3 | Donnée à écrire
|}
Les ''timings'' vus plus haut ne sont effectivement observés que si la mémoire est assez rapide. Mais le 8086 pouvait être combiné avec des mémoires RAM assez diverses, certaines plus lentes que d'autres. Si la mémoire est trop lente, elle peut prendre plusieurs cycles avant de renvoyer la donnée lue ou d'accepter la donnée à écrire. Pour gérer la situation, le processeur pouvait attendre que la mémoire réponde, avec des ''wait states''.
Pour cela, la mémoire prévenait quand la donnée lue était disponible, ou quand une écriture était terminée. Le processeur avait une entrée READY qui indiquait que la mémoire était disponible ou qu'elle avait terminé son travail. Lors d'une lecture, la mémoire mettait cette entrée READY à 1 quand la donnée lue était disponible sur le bus mémoire. Idem pour une écriture. L'implémentation dans l'unité mémoire est très simple : le processeur restait dans le cycle T3 en attendant que l'entrée READY soit à 1. Lors d'une écriture, cela maintenait à donnée à écrire sur le bus mémoire. Pour une lecture, cela permettait de laisser le registre d’interfaçage des données connecté au bus mémoire, en attendant la donnée lue.
Nous venons de voir que le processeur pouvait rester dans l'état T3 si le besoin s'en fait sentir. Mais cette astuce est utilisée dans d'autres situations. Par exemple, le processeur dispose d'une instruction HALT, qui le déconnecte totalement du bus. Le processeur reste déconnecté tant qu'il ne reçoit pas un RESET ou une interruption. Il s'agit d'une forme limitée de mise en veille, qui n'utilisait pas de méthodes pour économiser de l'énergie. L'instruction HALT était implémentée en restant en permanence dans l'état T2. Le processeur attendait d'avoir terminé toute instruction précédente, puis lançait une pseudo-lecture et restait en état T1 en attendant une interruption ou un RESET.
Le registre d’interfaçage pour les adresses est connecté sur le bus mémoire lors du premier cycle, celui pour les données l'est à des cycles différents suivant l'opération demandée. La connexion/déconnexion des registres d’interfaçage se fait en utilisant des circuits trois-états. Il y a une couche de circuits trois-états entre le registre d’interfaçage et le bus mémoire, qui permet de connecter/déconnecter les registres du bus mémoire, mais aussi d'imposer le sens de transfert suivant qu'on fasse une lecture ou une écriture.
[[File:Circuit d'interfacage mémoire du 8086.png|centre|vignette|upright=2|Circuit d’interfaçage mémoire du 8086.]]
Le circuit en question est un circuit séquentiel assez simple, une petite "machine à état" comme on le dit en termes techniques. Le circuit séquentiel commande les circuits trois-états. Une implémentation naïve serait d'utiliser un compteur pouvant compter de 0 à 3, avec des comparateurs. Il faut, en tout :
* Un comparateur qui détermine si le compteur est au cycle 1. Si c'est le cas, il connecte le registre d’interfaçage d'adresse sur le bus mémoire.
* Un autre comparateur détermine si une lecture est en cours et que l'on est dans le troisième cycle. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode lecture.
* Un autre comparateur détermine si une écriture est en cours et que l'on est dans les cycles adéquats. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode écriture.
Afin de simplifier fortement le circuit, le compteur utilisé utilise la représentation ''one hot''. Pour rappel, cela veut dire qu'il y a une bascule pour chaque valeur que peut prendre le compteur. Le compteur ''one hot'' est composé de quatre bascules. La première bascule est à 1 pour le cycle T1, la seconde est à 1 pour le cycle T2, la troisième est à 1 pour le cycle T3, la quatrième l'est pour le cycle T4. Ainsi, on peut se passer de certains comparateurs. Par exemple, le comparateur pour l'adresse disparait : la bascule commande directement les circuits trois-états. Les deux autres comparateurs se transforment des circuits à respectivement une ou quatre portes logiques.
Incrémenter le compteur se fait cependant sous certaines conditions. La machine à état contient des circuits qui décident quand incrémenter le compteur. Pour cela, il y a un circuit qui détermine quand on peut passer du cycle T1 au cycle T2, un autre pour le passage de T2 à T3, etc. Les circuits en question sont un paquet de portes logiques assez simple. Ils reçoivent des signaux de commande provenant du séquenceur, ainsi que des entrées comme l'entrée READY pour les ''wait states''.
[[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|centre|vignette|upright=2|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]]
Maintenant, parlons des problématiques liées au calcul d'adresse. Tous les accès mémoire demandent de faire un calcul d'adresse, en raison de l'usage de la fonctionnalité de segmentation. Et ce calcul d'adresse prend deux cycles. L'adresse est calculée, puis que l'accès mémoire est réalisé. En tout, cela fait 6 cycles : 2 pour le calcul d'adresse, puis les cycles T1, T2, T3 et T4.
Une optimisation importante est que deux accès mémoire peuvent se recouvrir. A savoir que pendant qu'un accès mémoire a lieu sur le bus mémoire, on calcule l'adresse de l'accès du suivant. Les deux ont lieu dans des circuits séparés, ce qui fait que c'est possible. Pour donner un exemple, on peut calculer l'adresse de la prochaine instruction, pendant que l'instruction mémoire en cours est exécutée. Le séquenceur du processeur est configuré pour démarrer le calcul d'adresse au bon moment. Il attend que le compteur soit dans l'état T3 et T4.
{|class="wikitable"
|-
! Unité de calcul
| class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || || || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || || || ||
|-
! Bus mémoire
| || || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2
|}
: La machine à état est décrite en détail dans cet article de blog : [https://www.righto.com/2024/04/intel-8088-bus-state-machine.html Talking to memory: Inside the Intel 8088 processor's bus interface state machine ].
==Le chemin de données et son réseau d'interconnexions==
Nous venons de voir que le chemin de données contient une unité de calcul (parfois plusieurs), des registres isolés, un banc de registre, une unité mémoire. Le tout est chapeauté par une unité de contrôle qui commande le chemin de données, qui fera l'objet des prochains chapitres. Mais il faut maintenant relier registres, ALU et unité mémoire pour que l'ensemble fonctionne. Pour cela, diverses interconnexions internes au processeur se chargent de relier le tout.
Sur les anciens processeurs, les interconnexions sont assez simples et se résument à un ou deux '''bus internes au processeur''', reliés au bus mémoire. C'était la norme sur des architectures assez ancienne, qu'on n'a pas encore vu à ce point du cours, appelées les architectures à accumulateur et à pile. Mais ce n'est plus la solution utilisée actuellement. De nos jours, le réseaux d'interconnexion intra-processeur est un ensemble de connexions point à point entre ALU/registres/unité mémoire. Et paradoxalement, cela rend plus facile de comprendre ce réseau d'interconnexion.
===Introduction propédeutique : l'implémentation des modes d'adressage principaux===
L'organisation interne du processeur dépend fortement des modes d'adressage supportés. Pour simplifier les explications, nous allons séparer les modes d'adressage qui gèrent les pointeurs et les autres. Suivant que le processeur supporte les pointeurs ou non, l'organisation des bus interne est légèrement différente. La différence se voit sur les connexions avec le bus d'adresse et de données.
Tout processeur gère au minimum le '''mode d'adressage absolu''', où l'adresse est intégrée à l'instruction. Le séquenceur extrait l'adresse mémoire de l'instruction, et l'envoie sur le bus d'adresse. Pour cela, le séquenceur est relié au bus d'adresse, le chemin de donnée est relié au bus de données. Le chemin de donnée n'est pas connecté au bus d'adresse, il n'y a pas d'autres connexions.
[[File:Chemin de données sans support des pointeurs.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs]]
Le '''support des pointeurs''' demande d'intégrer des modes d'adressage dédiés : l'adressage indirect à registre, l'adresse base + indice, et les autres. Les pointeurs sont stockés dans le banc de registre et sont modifiés par l'unité de calcul. Pour supporter les pointeurs, le chemin de données est connecté sur le bus d'adresse avec le séquenceur. Suivant le mode d'adressage, le bus d'adresse est relié soit au chemin de données, soit au séquenceur.
[[File:Chemin de données avec support des pointeurs.png|centre|vignette|upright=2|Chemin de données avec support des pointeurs]]
Pour terminer, il faut parler des instructions de '''copie mémoire vers mémoire''', qui copient une donnée d'une adresse mémoire vers une autre. Elles ne se passent pas vraiment dans le chemin de données, mais se passent purement au niveau des registres d’interfaçage. L'usage d'un registre d’interfaçage unique permet d'implémenter ces instructions très facilement. Elle se fait en deux étapes : on copie la donnée dans le registre d’interfaçage, on l'écrit en mémoire RAM. L'adresse envoyée sur le bus d'adresse n'est pas la même lors des deux étapes.
===Le banc de registre est multi-port, pour gérer nativement les opérations dyadiques===
Les architectures RISC et CISC incorporent un banc de registre, qui est connecté aux unités de calcul et au bus mémoire. Et ce banc de registre peut être mono-port ou multiport. S'il a existé d'anciennes architectures utilisant un banc de registre mono-port, elles sont actuellement obsolètes. Nous les aborderons dans un chapitre dédié aux architectures dites canoniques, mais nous pouvons les laisser de côté pour le moment. De nos jours, tous les processeurs utilisent un banc de registre multi-port.
[[File:Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres).png|centre|vignette|upright=2|Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres)]]
Le banc de registre multiport est optimisé pour les opérations dyadiques. Il dispose précisément de deux ports de lecture et d'un port d'écriture pour l'écriture. Un port de lecture par opérande et le port d'écriture pour enregistrer le résultat. En clair, le processeur peut lire deux opérandes et écrire un résultat en un seul cycle d'horloge. L'avantage est que les opérations simples ne nécessitent qu'une micro-opération, pas plus.
[[File:ALU data paths.svg|centre|vignette|upright=1.5|Processeur LOAD-STORE avec un banc de registre multiport, avec les trois ports mis en évidence.]]
===Une architecture LOAD-STORE basique, avec adressage absolu===
Voyons maintenant comment l'implémentation d'une architecture RISC très simple, qui ne supporte pas les adressages pour les pointeurs, juste les adressages inhérent (à registres) et absolu (par adresse mémoire). Les instructions LOAD et STORE utilisent l'adressage absolu, géré par le séquenceur, reste à gérer l'échange entre banc de registres et bus de données. Une lecture LOAD relie le bus de données au port d'écriture du banc de registres, alors que l'écriture relie le bus au port de lecture du banc de registre. Pour cela, il faut ajouter des interconnexions, comme illustré par le schéma ci-dessous.
[[File:Bus interne au processeur sur archi LOAD STORE avec banc de registres multiport.png|centre|vignette|upright=2|Organisation interne d'une architecture LOAD STORE avec banc de registres multiport. Nous n'avons pas représenté les signaux de commandes envoyés par le séquenceur au chemin de données.]]
Ajoutons ensuite les instructions de copie entre registres, souvent appelées instruction COPY ou MOV. Elles existent sur la plupart des architectures LOAD-STORE. Une première solution boucle l'entrée du banc de registres sur son entrée, ce qui ne sert que pour les copies de registres.
[[File:Chemin de données d'une architecture LOAD-STORE.png|centre|vignette|upright=2|Chemin de données d'une architecture LOAD-STORE]]
Mais il existe une seconde solution, qui ne demande pas de modifier le chemin de données. Il est possible de faire passer les copies de données entre registres par l'ALU. Lors de ces copies, l'ALU une opération ''Pass through'', à savoir qu'elle recopie une des opérandes sur sa sortie. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, dans le sens où cela permet d'économiser des multiplexeurs. Mais nous verrons cela sous peu. D'ailleurs, dans la suite du chapitre, nous allons partir du principe que les copies entre registres passent par l'ALU, afin de simplifier les schémas.
===L'ajout des modes d'adressage indirects à registre pour les pointeurs===
Passons maintenant à l'implémentation des modes d'adressages pour les pointeurs. Avec eux, l'adresse mémoire à lire/écrire n'est pas intégrée dans une instruction, mais est soit dans un registre, soit calculée par l'ALU.
Le premier mode d'adressage de ce type est le mode d'adressage indirect à registre, où l'adresse à lire/écrire est dans un registre. L'implémenter demande donc de connecter la sortie du banc de registres au bus d'adresse. Il suffit d'ajouter un MUX en sortie d'un port de lecture.
[[File:Chemin de données à trois bus.png|centre|vignette|upright=2|Chemin de données à trois bus.]]
Le mode d'adressage base + indice est un mode d'adressage où l'adresse à lire/écrire est calculée à partir d'une adresse et d'un indice, tous deux présents dans un registre. Le calcul de l'adresse peut être fait soit dans l'ALU entière, soit dans une AGU dédiée.
* Si l'unité mémoire intègre une AGU, il suffit de relier l'unité mémoire aux deux ports de lecture du banc de registre. Le schéma du dessous montre ce qu'il en est avec un calcul d'adresse fait dans l'ALU entière.
* S'il est réalisé dans l'ALU entière, on doit connecter la sortie de l'unité de calcul au bus d'adresse. Il est possible d'ajouter un registre sur cette connexion, afin de simplifier l'implémentation. L'instruction mémoire se fait alors en deux étapes : un calcul d'adresse, l'accès mémoire. L'unité mémoire lit l'adresse à lire dans le registre intercalé entre l'ALU entière et l'unité mémoire.
[[File:Bus avec adressage base+index.png|centre|vignette|upright=2|Bus avec adressage base+index]]
Le chemin de données précédent gère aussi le mode d'adressage indirect avec pré-décrément. Pour rappel, ce mode d'adressage est une variante du mode d'adressage indirect, qui utilise une pointeur/adresse stocké dans un registre. La différence est que ce pointeur est décrémenté avant d'être envoyé sur le bus d'adresse. L'implémentation matérielle est la même que pour le mode Base + Indice : l'adresse est lue depuis les registres, décrémentée dans l'ALU, et envoyée sur le bus d'adresse.
Le schéma précédent montre que le bus d'adresse est connecté à un MUX avant l'ALU et un autre MUX après. Mais il est possible de se passer du premier MUX, utilisé pour le mode d'adressage indirect à registre. La condition est que l'ALU supporte l'opération ''pass through'', un NOP, qui recopie une opérande sur sa sortie. L'ALU fera une opération NOP pour le mode d'adressage indirect à registre, un calcul d'adresse pour le mode d'adressage base + indice. Par contre, faire ainsi rendra l'adressage indirect légèrement plus lent, vu que le temps de passage dans l'ALU sera compté.
[[File:Bus avec adressage indirect.png|centre|vignette|upright=2|Bus avec adressages pour les pointeurs, simplifié.]]
Dans ce qui va suivre, nous allons partir du principe que le processeur est implémenté en suivant le schéma précédent, afin d'avoir des schéma plus lisibles.
===L'adressage immédiat et les modes d'adressages exotiques===
Passons maintenant au mode d’adressage immédiat, qui permet de préciser une constante dans une instruction directement. La constante est extraite de l'instruction par le séquenceur, puis insérée au bon endroit dans le chemin de données. Pour les opérations arithmétiques/logiques/branchements, il faut insérer la constante extraite sur l'entrée de l'ALU. Sur certains processeurs, la constante peut être négative et doit alors subir une extension de signe dans un circuit spécialisé.
[[File:Chemin de données - Adressage immédiat avec extension de signe.png|centre|vignette|upright=2|Chemin de données - Adressage immédiat avec extension de signe.]]
L'implémentation précédente gère aussi les modes d'adressage base + décalage et absolu indexé. Pour rappel, le premier ajoute une constante à une adresse prise dans les registres, le second prend une adresse constante et lui ajoute un indice pris dans les registres. Dans les deux cas, on lit un registre, extrait une constante/adresse de l’instruction, additionne les deux dans l'ALU, avant d'envoyer le résultat sur le bus d'adresse. La seule difficulté est de désactiver l'extension de signe pour les adresses.
Le mode d'adressage absolu peut être traité de la même manière, si l'ALU est capable de faire des NOPs. L'adresse est insérée au même endroit que pour le mode d'adressage immédiat, parcours l'unité de calcul inchangée parce que NOP, et termine sur le bus d'adresse.
[[File:Chemin de données avec une ALU capable de faire des NOP.png|centre|vignette|upright=2|Chemin de données avec adressage immédiat étendu pour gérer des adresses.]]
Passons maintenant au cas particulier d'une instruction MOV qui copie une constante dans un registre. Il n'y a rien à faire si l'unité de calcul est capable d'effectuer une opération NOP/''pass through''. Pour charger une constante dans un registre, l'ALU est configurée pour faire un NOP, la constante traverse l'ALU et se retrouve dans les registres. Si l'ALU ne gère pas les NOP, la constante doit être envoyée sur l'entrée d'écriture du banc de registres, à travers un MUX dédié.
[[File:Implémentation de l'adressage immédiat dans le chemin de données.png|centre|vignette|upright=2|Implémentation de l'adressage immédiat dans le chemin de données]]
===Les architectures CISC : les opérations ''load-op''===
Tout ce qu'on a vu précédemment porte sur les processeurs de type LOAD-STORE, souvent confondus avec les processeurs de type RISC, où les accès mémoire sont séparés des instructions utilisant l'ALU. Il est maintenant temps de voir les processeurs CISC, qui gèrent des instructions ''load-op'', qui peuvent lire une opérande depuis la mémoire.
L'implémentation des opérations ''load-op'' relie le bus de donnée directement sur une entrée de l'unité de calcul, en utilisant encore une fois un multiplexeur. L'implémentation parait simple, mais c'est parce que toute la complexité est déportée dans le séquenceur. C'est lui qui se charge de détecter quand la lecture de l'opérande est terminée, quand l'opérande est disponible.
Les instructions ''load-op'' s'exécutent en plusieurs étapes, en plusieurs micro-opérations. Il y a typiquement une étape pour l'opérande à lire en mémoire et une étape de calcul. L'usage d'un registre d’interfaçage permet d'implémenter les instructions ''load-op'' très facilement. Une opération ''load-op'' charge l'opérande en mémoire dans un registre d’interfaçage, puis relier ce registre d’interfaçage sur une des entrées de l'ALU. Un simple multiplexeur suffit pour implémenter le tout, en plus des modifications adéquates du séquenceur.
[[File:Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire.png|centre|vignette|upright=2|Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire]]
Supporter les instructions multi-accès (qui font plusieurs accès mémoire) ne modifie pas fondamentalement le réseau d'interconnexion, ni le chemin de données La raison est que supporter les instructions multi-accès se fait au niveau du séquenceur. En réalité, les accès mémoire se font en série, l'un après l'autre, sous la commande du séquenceur qui émet plusieurs micro-opérations mémoire consécutives. Les données lues sont placées dans des registres d’interactivement mémoire, ce qui demande d'ajouter des registres d’interfaçage mémoire en plus.
==Annexe : le cas particulier du pointeur de pile==
Le pointeur de pile est un registre un peu particulier. Il peut être placé dans le chemin de données ou dans le séquenceur, voire dans l'unité de chargement, tout dépend du processeur. Tout dépend de si le pointeur de pile gère une pile d'adresses de retour ou une pile d'appel.
===Le pointeur de pile non-adressable explicitement===
Avec une pile d'adresse de retour, le pointeur de pile n'est pas adressable explicitement, il est juste adressé implicitement par des instructions d'appel de fonction CALL et des instructions de retour de fonction RET. Le pointeur de pile est alors juste incrémenté ou décrémenté par un pas constant, il ne subit pas d'autres opérations, son adressage est implicite. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Il n'y a pas besoin de le relier au chemin de données, vu qu'il n'échange pas de données avec les autres registres. Il y a alors plusieurs solutions, mais la plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Quelques processeurs simples disposent d'une pile d'appel très limitée, où le pointeur de pile n'est pas adressable explicitement. Il est adressé implicitement par les instruction CALL, RET, mais aussi PUSH et POP, mais aucune autre instruction ne permet cela. Là encore, le pointeur de pile ne communique pas avec les autres registres. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Là encore, le plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Dans les deux cas, le pointeur de pile est placé dans l'unité de contrôle, le séquenceur, et est associé à un incrémenteur dédié. Il se trouve que cet incrémenteur est souvent partagé avec le ''program counter''. En effet, les deux sont des adresses mémoire, qui sont incrémentées et décrémentées par pas constants, ne subissent pas d'autres opérations (si ce n'est des branchements, mais passons). Les ressemblances sont suffisantes pour fusionner les deux circuits. Ils peuvent donc avoir un '''incrémenteur partagé'''.
L'incrémenteur en question est donc partagé entre pointeur de pile, ''program counter'' et quelques autres registres similaires. Par exemple, le Z80 intégrait un registre pour le rafraichissement mémoire, qui était réalisé par le CPU à l'époque. Ce registre contenait la prochaine adresse mémoire à rafraichir, et était incrémenté à chaque rafraichissement d'une adresse. Et il était lui aussi intégré au séquenceur et incrémenté par l'incrémenteur partagé.
[[File:Organisation interne d'une architecture à pile.png|centre|vignette|upright=2|Organisation interne d'une architecture à pile]]
===Le pointeur de pile adressable explicitement===
Maintenant, étudions le cas d'une pile d'appel, précisément d'une pile d'appel avec des cadres de pile de taille variable. Sous ces conditions, le pointeur de pile est un registre adressable, avec un nom/numéro de registre dédié. Tel est par exemple le cas des processeurs x86 avec le registre ESP (''Extended Stack Pointer''). 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 calculs d'adresse, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres.
Dans ce cas, la meilleure solution est de placer le pointeur de pile dans le banc de registre généraux, avec les autres registres entiers. En faisant cela, la manipulation du pointeur de pile est faite par l'unité de calcul entière, pas besoin d'utiliser un incrémenteur dédiée. Il a existé des processeurs qui mettaient le pointeur de pile dans le banc de registre, mais l'incrémentaient avec un incrémenteur dédié, mais nous les verrons dans le chapitre sur les architectures à accumulateur. La raison est que sur les processeurs concernés, les adresses ne faisaient pas la même taille que les données : c'était des processeurs 8 bits, qui géraient des adresses de 16 bits.
==Annexe : l'implémentation du système d'''aliasing'' des registres des CPU x86==
Il y a quelques chapitres, nous avions parlé du système d'''aliasing'' des registres des CPU x86. Pour rappel, il permet de donner plusieurs noms de registre pour un même registre. Plus précisément, pour un registre 64 bits, le registre complet aura un nom de registre, les 32 bits de poids faible auront leur nom de registre dédié, idem pour les 16 bits de poids faible, etc. Il est possible de faire des calculs sur ces moitiés/quarts/huitièmes de registres sans problème.
===L'''aliasing'' du 8086, pour les registres 16 bits===
[[File:Register 8086.PNG|vignette|Register 8086]]
L'implémentation de l'''aliasing'' est apparue sur les premiers CPU Intel 16 bits, notamment le 8086. En tout, ils avaient quatre registres généraux 16 bits : AX, BX, CX et DX. Ces quatre registres 16 bits étaient coupés en deux octets, chacun adressable. Par exemple, le registre AX était coupé en deux octets nommés AH et AL, chacun ayant son propre nom/numéro de registre. Les instructions d'addition/soustraction pouvaient manipuler le registre AL, ou le registre AH, ce qui modifiait les 8 bits de poids faible ou fort selon le registre choisit.
Le banc de registre ne gére que 4 registres de 16 bits, à savoir AX, BX, CX et DX. Lors d'une lecture d'un registre 8 bits, le registre 16 bit entier est lu depuis le banc de registre, mais les bits inutiles sont ignorés. Par contre, l'écriture peut se faire soit avec 16 bits d'un coup, soit pour seulement un octet. Le port d'écriture du banc de registre peut être configuré de manière à autoriser l'écriture soit sur les 16 bits du registre, soit seulement sur les 8 bits de poids faible, soit écrire dans les 8 bits de poids fort.
[[File:Port d'écriture du banc de registre du 8086.png|centre|vignette|upright=2.5|Port d'écriture du banc de registre du 8086]]
Une opération sur un registre 8 bits se passe comme suit. Premièrement, on lit le registre 16 bits complet depuis le banc de registre. Si l'on a sélectionné l'octet de poids faible, il ne se passe rien de particulier, l'opérande 16 bits est envoyée directement à l'ALU. Mais si on a sélectionné l'octet de poids fort, la valeur lue est décalée de 7 rangs pour atterrir dans les 8 octets de poids faible. Ensuite, l'unité de calcul fait un calcul avec cet opérande, un calcul 16 bits tout ce qu'il y a de plus classique. Troisièmement, le résultat est enregistré dans le banc de registre, en le configurant convenablement. La configuration précise s'il faut enregistrer le résultat dans un registre 16 bits, soit seulement dans l'octet de poids faible/fort.
Afin de simplifier le câblage, les 16 bits des registres AX/BX/CX/DX sont entrelacés d'une manière un peu particulière. Intuitivement, on s'attend à ce que les bits soient physiquement dans le même ordre que dans le registre : le bit 0 est placé à côté du bit 1, suivi par le bit 2, etc. Mais à la place, l'octet de poids fort et de poids faible sont mélangés. Deux bits consécutifs appartiennent à deux octets différents. Le tout est décrit dans le tableau ci-dessous.
{|class="wikitable"
|-
! Registre 16 bits normal
| class="f_bleu" | 15
| class="f_bleu" | 14
| class="f_bleu" | 13
| class="f_bleu" | 12
| class="f_bleu" | 11
| class="f_bleu" | 10
| class="f_bleu" | 9
| class="f_bleu" | 8
| class="f_rouge" | 7
| class="f_rouge" | 6
| class="f_rouge" | 5
| class="f_rouge" | 4
| class="f_rouge" | 3
| class="f_rouge" | 2
| class="f_rouge" | 1
| class="f_rouge" | 0
|-
! Registre 16 bits du 8086
| class="f_bleu" | 15
| class="f_rouge" | 7
| class="f_bleu" | 14
| class="f_rouge" | 6
| class="f_bleu" | 13
| class="f_rouge" | 5
| class="f_bleu" | 12
| class="f_rouge" | 4
| class="f_bleu" | 11
| class="f_rouge" | 3
| class="f_bleu" | 10
| class="f_rouge" | 2
| class="f_bleu" | 9
| class="f_rouge" | 1
| class="f_bleu" | 8
| class="f_rouge" | 0
|}
En faisant cela, le décaleur en entrée de l'ALU est bien plus simple. Il y a 8 multiplexeurs, mais le câblage est bien plus simple. Par contre, en sortie de l'ALU, il faut remettre les bits du résultat dans l'ordre adéquat, celui du registre 8086. Pour cela, les interconnexions sur le port d'écriture sont conçues pour. Il faut juste mettre les fils de sortie de l'ALU sur la bonne entrée, par besoin de multiplexeurs.
===L'''aliasing'' sur les processeurs x86 32/64 bits===
Les processeurs x86 32 et 64 bits ont un système d'''aliasing'' qui complète le système précédent. Les processeurs 32 bits étendent les registres 16 bits existants à 32 bits. Pour ce faire, le registre 32 bit a un nouveau nom de registre, distincts du nom de registre utilisé pour l'ancien registre 16 bits. Il est possible d'adresser les 16 bits de poids faible de ce registre, avec le même nom de registre que celui utilisé pour le registre 16 sur les processeurs d'avant. Même chose avec les processeurs 64, avec l'ajout d'un nouveau nom de registre pour adresser un registre de 64 bit complet.
En soit, implémenter ce système n'est pas compliqué. Prenons le cas du registre RAX (64 bits), et de ses subdivisions nommées EAX (32 bits), AX (16 bits). À l'intérieur du banc de registre, il n'y a que le registre RAX. Le banc de registre ne comprend qu'un seul nom de registre : RAX. Les subdivisions EAX et AX n'existent qu'au niveau de l'écriture dans le banc de registre. L'écriture dans le banc de registre est configurable, de manière à ne modifier que les bits adéquats. Le résultat d'un calcul de l'ALU fait 64 bits, il est envoyé sur le port d'écriture. À ce niveau, soit les 64 bits sont écrits dans le registre, soit seulement les 32/16 bits de poids faible. Le système du 8086 est préservé pour les écritures dans les 16 bits de poids faible.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les composants d'un processeur
| prevText=Les composants d'un processeur
| next=L'unité de chargement et le program counter
| nextText=L'unité de chargement et le program counter
}}
</noinclude>
bmx5pw2n7iwzcu7d22hfvh18j917peo
772063
772062
2026-09-12T00:47:40Z
Mewtow
31375
/* L'ajout des modes d'adressage indirects à registre pour les pointeurs */
772063
wikitext
text/x-wiki
Comme vu précédemment, le '''chemin de donnée''' est l'ensemble des composants dans lesquels circulent les données dans le processeur. Il comprend l'unité de calcul, les registres, l'unité de communication avec la mémoire, et le ou les interconnexions qui permettent à tout ce petit monde de communiquer. Dans ce chapitre, nous allons voir ces composants en détail.
==Les unités de calcul==
Le processeur contient des circuits capables de faire des calculs arithmétiques, des opérations logiques, et des comparaisons, qui sont regroupés dans une unité de calcul appelée '''unité arithmétique et logique'''. Certains préfèrent l’appellation anglaise ''arithmetic and logic unit'', ou ALU. Par défaut, ce terme est réservé aux unités de calcul qui manipulent des nombres entiers. Les unités de calcul spécialisées pour les calculs flottants sont désignées par le terme "unité de calcul flottant", ou encore FPU (''Floating Point Unit'').
L'interface d'une unité de calcul est assez simple : on a des entrées pour les opérandes et une sortie pour le résultat du calcul. De plus, les instructions de comparaisons ou de calcul peuvent mettre à jour le registre d'état, qui est relié à une autre sortie de l’unité de calcul. Une autre entrée, l''''entrée de sélection de l'instruction''', spécifie l'opération à effectuer. Elle sert à configurer l'unité de calcul pour faire une addition et pas une multiplication, par exemple. Sur cette entrée, on envoie un numéro qui précise l'opération à effectuer. La correspondance entre ce numéro et l'opération à exécuter dépend de l'unité de calcul. Sur les processeurs où l'encodage des instructions est "simple", une partie de l'opcode de l'instruction est envoyé sur cette entrée.
[[File:Unité de calcul usuelle.png|centre|vignette|upright=2|Unité de calcul usuelle.]]
Il faut signaler que les processeurs modernes possèdent plusieurs unités de calcul, toutes reliées aux registres. Cela permet d’exécuter plusieurs calculs en même temps dans des unités de calcul différentes, afin d'augmenter les performances du processeur. Diverses technologies, abordées dans la suite du cours permettent de profiter au mieux de ces unités de calcul : pipeline, exécution dans le désordre, exécution superscalaire, jeux d'instructions VLIW, etc. Mais laissons cela de côté pour le moment.
===L'ALU entière : additions, soustractions, opérations bit à bit===
Un processeur contient plusieurs ALUs spécialisées. La principale, présente sur tous les processeurs, est l''''ALU entière'''. Elle s'occupe uniquement des opérations sur des nombres entiers, les nombres flottants sont gérés par une ALU à part. Elle gère des opérations simples : additions, soustractions, opérations bit à bit, parfois des décalages/rotations. Par contre, elle ne gère pas la multiplication et la division, qui sont prises en charge par un circuit multiplieur/diviseur à part.
L'ALU entière a déjà été vue dans un chapitre antérieur, nommé "Les unités arithmétiques et logiques entières (simples)", qui expliquait comment en concevoir une. Nous avions vu qu'une ALU entière est une sorte de circuit additionneur-soustracteur amélioré, ce qui explique qu'elle gère des opérations entières simples, mais pas la multiplication ni la division. Nous ne reviendrons pas dessus. Cependant, il y a des choses à dire sur leur intégration au processeur.
Une ALU entière gère souvent une opération particulière, qui ne fait rien et recopie simplement une de ses opérandes sur sa sortie. L'opération en question est appelée l''''opération ''Pass through''''', encore appelée opération NOP. Elle est implémentée en utilisant un simple multiplexeur, placé en sortie de l'ALU. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, d'économiser des multiplexeurs. Mais nous verrons cela sous peu.
[[File:ALU avec opération NOP.png|centre|vignette|upright=2|ALU avec opération NOP.]]
Avant l'invention du microprocesseur, le processeur n'était pas un circuit intégré unique. L'ALU, le séquenceur et les registres étaient dans des puces séparées. Les ALU étaient vendues séparément et manipulaient des opérandes de 4/8 bits, les ALU 4 bits étaient très fréquentes. Si on voulait créer une ALU pour des opérandes plus grandes, il fallait construire l'ALU en combinant plusieurs ALU 4/8 bits. Par exemple, l'ALU des processeurs AMD Am2900 est une ALU de 16 bits composée de plusieurs sous-ALU de 4 bits. Cette technique qui consiste à créer des unités de calcul à partir d'unités de calcul plus élémentaires s'appelle en jargon technique du '''bit slicing'''. Nous en avions parlé dans le chapitre sur les unités de calcul, aussi nous n'en reparlerons pas plus ici.
L'ALU manipule des opérandes codées sur un certain nombre de bits. Par exemple, une ALU peut manipuler des entiers codés sur 8 bits, sur 16 bits, etc. En général, la taille des opérandes de l'ALU est la même que la taille des registres. Un processeur 32 bits, avec des registres de 32 bit, a une ALU de 32 bits. C'est intuitif, et cela rend l'implémentation du processeur bien plus facile. Mais il y a quelques exceptions, où l'ALU manipule des opérandes plus petits que la taille des registres. Par exemple, de nombreux processeurs 16 bits, avec des registres de 16 bits, utilisent une ALU de 8 bits. Un autre exemple assez connu est celui du Motorola 68000, qui était un processeur 32 bits, mais dont l'ALU faisait juste 16 bits. Son successeur, le 68020, avait lui une ALU de 32 bits.
Sur de tels processeurs, les calculs sont fait en plusieurs passes. Par exemple, avec une ALU 8 bit, les opérations sur des opérandes 8 bits se font en un cycle d'horloge, celles sur 16 bits se font en deux cycles, celles en 32 en quatre, etc. Si un programme manipule assez peu d'opérandes 16/32/64 bits, la perte de performance est assez faible. Diverses techniques visent à améliorer les performances, mais elles ne font pas de miracles. Par exemple, vu que l'ALU est plus courte, il est possible de la faire fonctionner à plus haute fréquence, pour réduire la perte de performance.
Pour comprendre comme est implémenté ce système de passes, prenons l'exemple du processeur 8 bit Z80. Ses registres entiers étaient des registres de 8 bits, alors que l'ALU était de 4 bits. Les calculs étaient faits en deux phases : une qui traite les 4 bits de poids faible, une autre qui traite les 4 bits de poids fort. Pour cela, les opérandes étaient placées dans des registres de 4 bits en entrée de l'ALU, plusieurs multiplexeurs sélectionnaient les 4 bits adéquats, le résultat était mémorisé dans un registre de résultat de 8 bits, un démultiplexeur plaçait les 4 bits du résultat au bon endroit dans ce registre. L'unité de contrôle s'occupait de la commande des multiplexeurs/démultiplexeurs. Les autres processeurs 8 ou 16 bits utilisent des circuits similaires pour faire leurs calculs en plusieurs fois.
[[File:ALU du Z80.png|centre|vignette|upright=2|ALU du Z80]]
Un exemple extrême est celui des des '''processeurs sériels''' (sous-entendu ''bit-sériels''), qui utilisent une '''ALU sérielle''', qui fait leurs calculs bit par bit, un bit à la fois. S'il a existé des processeurs de 1 bit, comme le Motorola MC14500B, la majeure partie des processeurs sériels étaient des processeurs 4, 8 ou 16 bits. L'avantage de ces ALU est qu'elles utilisent peu de transistors, au détriment des performances par rapport aux processeurs non-sériels. Mais un autre avantage est qu'elles peuvent gérer des opérandes de grande taille, avec plus d'une trentaine de bits, sans trop de problèmes.
===Les circuits multiplieurs et diviseurs===
Les processeurs modernes ont une ALU pour les opérations simples (additions, décalages, opérations logiques), couplée à une ALU pour les multiplications, un circuit multiplieur séparé. Précisons qu'il ne sert pas à grand chose de fusionner le circuit multiplieur avec l'ALU, mieux vaut les garder séparés par simplicité. Les processeurs haute performance disposent systématiquement d'un circuit multiplieur et gèrent la multiplication dans leur jeu d'instruction.
Le cas de la division est plus compliqué. La présence d'un circuit multiplieur est commune, mais les circuits diviseurs sont eux très rares. Leur cout en circuit est globalement le même que pour un circuit multiplieur, mais le gain en performance est plus faible. Le gain en performance pour la multiplication est modéré car il s'agit d'une opération très fréquente, alors qu'il est très faible pour la division car celle-ci est beaucoup moins fréquente.
Pour réduire le cout en circuits, il arrive que l'ALU pour les multiplications gère à la fois la multiplication et la division. Les circuits multiplieurs et diviseurs sont en effet très similaires et partagent beaucoup de points communs. Généralement, la fusion se fait pour les multiplieurs/diviseurs itératifs.
===Le ''barrel shifter''===
On vient d'expliquer que la présence de plusieurs ALU spécialisée est très utile pour implémenter des opérations compliquées à insérer dans une unité de calcul normale, comme la multiplication et la division. Mais les décalages sont aussi dans ce cas, de même que les rotations. Nous avions vu il y a quelques chapitres qu'ils sont réalisés par un circuit spécialisé, appelé un ''barrel shifter'', qu'il est difficile de fusionner avec une ALU normale. Aussi, beaucoup de processeurs incorporent un ''barrel shifter'' séparé de l'ALU.
Les processeurs ARM utilise un ''barrel shifter'', mais d'une manière un peu spéciale. On a vu il y a quelques chapitres que si on fait une opération logique, une addition, une soustraction ou une comparaison, la seconde opérande peut être décalée automatiquement. L'instruction incorpore le type de de décalage à faire et par combien de rangs il faut décaler directement à côté de l'opcode. Cela simplifie grandement les calculs d'adresse, qui se font en une seule instruction, contre deux ou trois sur d'autres architectures. Et pour cela, l'ALU proprement dite est précédée par un ''barrel shifter'',une seconde ALU spécialisée dans les décalages. Notons que les instructions MOV font aussi partie des instructions où la seconde opérande (le registre source) peut être décalé : cela signifie que les MOV passent par l'ALU, qui effectue alors un NOP, une opération logique OUI.
===Les unités de calcul spécialisées===
Un processeur peut disposer d’unités de calcul séparées de l'unité de calcul principale, spécialisées dans les décalages, les divisions, etc. Et certaines d'entre elles sont spécialisées dans des opérations spécifiques, qui ne sont techniquement pas des opérations entières, sur des nombres entiers.
[[File:Unité de calcul flottante, intérieur.png|vignette|upright=1|Unité de calcul flottante, intérieur]]
Depuis les années 90-2000, presque tous les processeurs utilisent une unité de calcul spécialisée pour les nombres flottants : la '''Floating-Point Unit''', aussi appelée FPU. En général, elle regroupe un additionneur-soustracteur flottant et un multiplieur flottant. Parfois, elle incorpore un diviseur flottant, tout dépend du processeur. Précisons que sur certains processeurs, la FPU et l'ALU entière ne vont pas à la même fréquence, pour des raisons de performance et de consommation d'énergie !
La FPU intègre un circuit multiplieur entier, utilisé pour les multiplications flottantes, afin de multiplier les mantisses entre elles. Quelques processeurs utilisaient ce multiplieur pour faire les multiplications entières. En clair, au lieu d'avoir un multiplieur entier séparé du multiplieur flottant, les deux sont fusionnés en un seul circuit. Il s'agit d'une optimisation qui a été utilisée sur quelques processeurs 32 bits, qui supportaient les flottants 64 bits (double précision). Les processeurs Atom étaient dans ce cas, idem pour l'Athlon première génération. Les processeurs modernes n'utilisent pas cette optimisation pour des raisons qu'on ne peut pas expliquer ici (réduction des dépendances structurelles, émission multiple).
De nombreux processeurs modernes disposent d'une unité de calcul spécialisée dans le calcul des conditions, tests et branchements. C’est notamment le cas sur les processeurs sans registre d'état, qui disposent de registres à prédicats. En général, les registres à prédicats sont placés à part des autres registres, dans un banc de registre séparé. L'unité de calcul normale n'est pas reliée aux registres à prédicats, alors que l'unité de calcul pour les branchements/test/conditions l'est. Les registres à prédicats sont situés juste en sortie de cette unité de calcul.
Il existe des unités de calcul spécialisées pour les calculs d'adresse. Elles ne supportent guère plus que des incrémentations/décrémentations, des additions/soustractions, et des décalages simples. Les autres opérations n'ont pas de sens avec des adresses. L'usage d'ALU spécialisées pour les adresses est un avantage sur les processeurs où les adresses ont une taille différente des données, ce qui est fréquent sur les anciens processeurs 8 bits.
==Les registres du processeur==
Après avoir vu l'unité de calcul, il est temps de passer aux registres d'un processeur. L'organisation des registres est généralement assez compliquée, avec quelques registres séparés des autres comme le registre d'état ou le ''program counter''. Les registres d'un processeur peuvent se classer en deux camps : soit ce sont des registres isolés, soit ils sont regroupés en paquets appelés banc de registres.
Un '''banc de registres''' (''register file'') est une RAM, dont chaque byte est un registre. Il regroupe un paquet de registres différents dans un seul composant, dans une seule mémoire. Dans processeur moderne, on trouve un ou plusieurs bancs de registres. La répartition des registres, à savoir quels registres sont dans le banc de registre et quels sont ceux isolés, est très variable suivant les processeurs.
[[File:Register File Simple.svg|centre|vignette|upright=1|Banc de registres simplifié.]]
===L'adressage du banc de registres===
Le banc de registre est une mémoire comme une autre, avec une entrée d'adresse qui permet de sélectionner le registre voulu. Plutot que d'adresse, nous allons parler d''''identifiant de registre'''. Le séquenceur forge l'identifiant de registre en fonction des registres sélectionnés. Dans les chapitres précédents, nous avions vu qu'il existe plusieurs méthodes pour sélectionner un registre, qui portent les noms de modes d'adressage. Et bien les modes d'adressage jouent un grand rôle dans la forge de l'identifiant de registre.
Pour rappel, sur la quasi-totalité des processeurs actuels, les registres généraux sont identifiés par un nom de registre, terme trompeur vu que ce nom est en réalité un numéro. En clair, les processeurs numérotent les registres, le numéro/nom du registre permettant de l'identifier. Par exemple, si je veux faire une addition, je dois préciser les deux registres pour les opérandes, et éventuellement le registre pour le résultat : et bien ces registres seront identifiés par un numéro. Mais tous les registres ne sont pas numérotés et ceux qui ne le sont pas sont adressés implicitement. Par exemple, le pointeur de pile sera modifié par les instructions qui manipulent la pile, sans que cela aie besoin d'être précisé par un nom de registre dans l'instruction.
Dans le cas le plus simple, les registres nommés vont dans le banc de registres, les registres adressés implicitement sont en-dehors, dans des registres isolés. L'idéntifiant de registre est alors simplement le nom de registre, le numéro. Le séquenceur extrait ce nom de registre de l'insutrction, avant de l'envoyer sur l'entrée d'adresse du banc de registre.
[[File:Adressage du banc de registres généruax.png|centre|vignette|upright=2|Adressage du banc de registres généraux]]
Dans un cas plus complexe, des registres non-nommés sont placés dans le banc de registres. Par exemple, les pointeurs de pile sont souvent placés dans le banc de registre, même s'ils sont adressés implicitement. Même des registres aussi importants que le ''program counter'' peuvent se mettre dans le banc de registre ! Nous verrons le cas du ''program counter'' dans le chapitre suivant, qui porte sur l'unité de chargement. Dans ce cas, le séquenceur forge l'identifiant de registre de lui-même. Dans le cas des registres nommés, il ajoute quelques bits aux noms de registres. Pour les registres adressés implicitement, il forge l'identifiant à partir de rien.
[[File:Adressage du banc de registre - cas général.png|centre|vignette|upright=2|Adressage du banc de registre - cas général]]
Nous verrons plus bas que dans certains cas, le nom de registre ne suffit pas à adresser un registre dans un banc de registre. Dans ce cas, le séquenceur rajoute des bits, comme dans l'exemple précédent. Tout ce qu'il faut retenir est que l'identifiant de registre est forgé par le séquenceur, qui se base entre autres sur le nom de registre s'il est présent, sur l'instruction exécutée dans le cas d'un registre adressé implicitement.
===Les registres généraux===
Pour rappel, les registres généraux peuvent mémoriser des entiers, des adresses, ou toute autre donnée codée en binaire. Ils sont souvent séparés des registres flottants sur les architectures modernes. Les registres généraux sont rassemblés dans un banc de registre dédié, appelé le '''banc de registres généraux'''. Le banc de registres généraux est une mémoire multiport, avec au moins un port d'écriture et deux ports de lecture. La raison est que les instructions lisent deux opérandes dans les registres et enregistrent leur résultat dans des registres. Le tout se marie bien avec un banc de registre à deux de lecture (pour les opérandes) et un d'écriture (pour le résultat).
[[File:Banc de registre multiports.png|centre|vignette|upright=2|Banc de registre multiports.]]
L'interface exacte dépend de si l'architecture est une architecture 2 ou 3 adresses. Pour rappel, la différence entre les deux tient dans la manière dont on précise le registre où enregistrer le résultat d'une opération. Avec les architectures 2-adresses, on précise deux registres : le premier sert à la fois comme opérande et pour mémoriser le résultat, l'autre sert uniquement d'opérande. Un des registres est donc écrasé pour enregistrer le résultat. Sur les architecture 3-adresses, on précise trois registres : deux pour les opérandes, un pour le résultat.
Les architectures 2-adresses ont un banc de registre où on doit préciser deux "adresses", deux noms de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Medium.svg|centre|vignette|upright=1.5|Register File d'une architecture à 2-adresses]]
Les architectures 3-adresses doivent rajouter une troisième entrée pour préciser un troisième nom de registre. L'interface du banc de registre est donc la suivante :
[[File:Register File Large.svg|centre|vignette|upright=1.5|Register File d'une architecture à 3-adresses]]
Rien n'empêche d'utiliser plusieurs bancs de registres sur un processeur qui utilise des registres généraux. La raison est une question d'optimisation. Au-delà d'un certain nombre de registres, il devient difficile d'utiliser un seul gros banc de registres. Il faut alors scinder le banc de registres en plusieurs bancs de registres séparés. Le problème est qu'il faut prévoir de quoi échanger des données entre les bancs de registres. Dans la plupart des cas, cette séparation est invisible du point de vue du langage machine. Sur d'autres processeurs, les transferts de données entre bancs de registres se font via une instruction spéciale, souvent appelée COPY.
===Les registres flottants : banc de registre séparé ou unifié===
Passons maintenant aux registres flottants. Intuitivement, on a des registres séparés pour les entiers et les flottants. Il est alors plus simple d'utiliser un banc de registres séparé pour les nombres flottants, à côté d'un banc de registre entiers. L'avantage est que les nombres flottants et entiers n'ont pas forcément la même taille, ce qui se marie bien avec deux bancs de registres, où la taille des registres est différente dans les deux bancs.
Mais d'autres processeurs utilisent un seul '''banc de registres unifié''', qui regroupe tous les registres de données, qu'ils soient entier ou flottants. Par exemple, c'est le cas des Pentium Pro, Pentium II, Pentium III, ou des Pentium M : ces processeurs ont des registres séparés pour les flottants et les entiers, mais ils sont regroupés dans un seul banc de registres. Avec cette organisation, un registre flottant et un registre entier peuvent avoir le même nom de registre en langage machine, mais l'adresse envoyée au banc de registres ne doit pas être la même : le séquenceur ajoute des bits au nom de registre pour former l'adresse finale.
[[File:Désambiguïsation de registres sur un banc de registres unifié.png|centre|vignette|upright=2|Désambiguïsation de registres sur un banc de registres unifié.]]
===Le registre d'état===
Le registre d'état fait souvent bande à part et n'est pas placé dans un banc de registres. En effet, le registre d'état est très lié à l'unité de calcul. Il reçoit des indicateurs/''flags'' provenant de la sortie de l'unité de calcul, et met ceux-ci à disposition du reste du processeur. Son entrée est connectée à l'unité de calcul, sa sortie est reliée au séquenceur et/ou au bus interne au processeur.
Le registre d'état est relié au séquenceur afin que celui-ci puisse gérer les instructions de branchement, qui ont parfois besoin de connaitre certains bits du registre d'état pour savoir si une condition a été remplie ou non. D'autres processeurs relient aussi le registre d'état au bus interne, ce qui permet de lire son contenu et de le copier dans un registre de données. Cela permet d'implémenter certaines instructions, notamment celles qui permettent de mémoriser le registre d'état dans un registre général.
[[File:Place du registre d'état dans le chemin de données.png|centre|vignette|upright=2|Place du registre d'état dans le chemin de données]]
L'ALU fournit une sortie différente pour chaque bit du registre d'état, la connexion du registre d'état est directe, comme indiqué dans le schéma suivant. Vous remarquerez que le bit de retenue est à la fois connecté à la sortie de l'ALU, mais aussi sur son entrée. Ainsi, le bit de retenue calculé par une opération peut être utilisé pour la suivante. Sans cela, diverses instructions comme les opérations ''add with carry'' ne seraient pas possibles.
[[File:AluStatusRegister.svg|centre|vignette|upright=2|Registre d'état et unit de calcul.]]
Il est techniquement possible de mettre le registre d'état dans le banc de registre, pour économiser un registre. La principale difficulté est que les instructions doivent faire deux écritures dans le banc de registre : une pour le registre de destination, une pour le registre d'état. Soit on utilise deux ports d'écriture, soit on fait les deux écritures l'une après l'autre. Dans les deux cas, le cout en performances et en transistors n'en vaut pas le cout. D'ailleurs, je ne connais aucun processeur qui utilise cette technique.
Il faut noter que le registre d'état n'existe pas forcément en tant que tel dans le processeur. Quelques processeurs, dont le 8086 d'Intel, utilisent des bascules dispersées dans le processeur au lieu d'un vrai registre d'état. Les bascules dispersées mémorisent chacune un bit du registre d'état et sont placées là où elles sont le plus utile. Les bascules utilisées pour les branchements sont proches du séquenceur, le bascules pour les bits de retenue sont placées proche de l'ALU, etc.
===Les registres à prédicats===
Les registres à prédicats remplacent le registre d'état sur certains processeurs. Pour rappel, les registres à prédicat sont des registres de 1 bit qui mémorisent les résultats des comparaisons et instructions de test. Ils sont nommés/numérotés, mais les numéros en question sont distincts de ceux utilisés pour les registres généraux.
Ils sont placés à part, dans un banc de registres séparé. Le banc de registres à prédicats a une entrée de 1 bit connectée à l'ALU et une sortie de un bit connectée au séquenceur. Le banc de registres à prédicats est parfois relié à une unité de calcul spécialisée dans les conditions/instructions de test. Pour rappel, certaines instructions permettent de faire un ET, un OU, un XOR entre deux registres à prédicats. Pour cela, l'unité de calcul dédiée aux conditions peut lire les registres à prédicats, pour combiner le contenu de plusieurs d'entre eux.
[[File:Banc de registre pour les registres à prédicats.png|centre|vignette|upright=2|Banc de registre pour les registres à prédicats]]
===Les registres dédiés aux interruptions===
Dans le chapitre sur les registres, nous avions vu que certains processeurs dupliquaient leurs registres architecturaux, pour accélérer les interruptions ou les appels de fonction. Dans le cas qui va nous intéresser, les interruptions avaient accès à leurs propres registres, séparés des registres architecturaux. Les processeurs de ce type ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. Si on peut utiliser deux bancs de registres séparés, il est aussi possible d'utiliser un banc de registre unifié pour les deux.
Sur certains processeurs, le banc de registre est dupliqué en plusieurs exemplaires. La technique est utilisée pour les interruptions. Certains processeurs ont deux ensembles de registres identiques : un dédié aux interruptions, un autre pour les programmes normaux. Les registres dans les deux ensembles ont les mêmes noms, mais le processeur choisit le bon ensemble suivant s'il est dans une interruption ou non. On peut utiliser deux bancs de registres séparés, un pour les interruptions, et un pour les programmes.
Sur d'autres processeurs, on utilise un banc de registre unifié pour les deux ensembles de registres. Les registres pour les interruptions sont dans les adresses hautes, les registres pour les programmes dans les adresses basses. Le choix entre les deux est réalisé par un bit qui indique si on est dans une interruption ou non, disponible dans une bascule du processeur. Appelons là la bascule I.
===Le fenêtrage de registres===
[[File:Fenetre de registres.png|vignette|upright=1|Fenêtre de registres.]]
Le '''fenêtrage de registres''' fait que chaque fonction a accès à son propre ensemble de registres, sa propre fenêtre de registres. Là encore, cette technique duplique chaque registre architectural en plusieurs exemplaires qui portent le même nom. Chaque ensemble de registres architecturaux forme une fenêtre de registre, qui contient autant de registres qu'il y a de registres architecturaux. Lorsqu'une fonction s’exécute, elle se réserve une fenêtre inutilisée, et peut utiliser les registres de la fenêtre comme bon lui semble : une fonction manipule le registre architectural de la fenêtre réservée, mais pas les registres avec le même nom dans les autres fenêtres.
Il peut s'implémenter soit avec un banc de registres unifié, soit avec un banc de registre par fenêtre de registres.
Il est possible d'utiliser des bancs de registres dupliqués pour le fenêtrage de registres. Chaque fenêtre de registre a son propre banc de registres. Le choix entre le banc de registre à utiliser est fait par un registre qui mémorise le numéro de la fenêtre en cours. Ce registre commande un multiplexeur qui permet de choisir le banc de registre adéquat.
[[File:Fenêtrage de registres au niveau du banc de registres.png|vignette|Fenêtrage de registres au niveau du banc de registres.]]
L'utilisation d'un banc de registres unifié permet d'implémenter facilement le fenêtrage de registres. Il suffit pour cela de regrouper tous les registres des différentes fenêtres dans un seul banc de registres. Il suffit de faire comme vu au-dessus : rajouter des bits au nom de registre pour faire la différence entre les fenêtres. Cela implique de se souvenir dans quelle fenêtre de registre on est actuellement, cette information étant mémorisée dans un registre qui stocke le numéro de la fenêtre courante. Pour changer de fenêtre, il suffit de modifier le contenu de ce registre lors d'un appel ou retour de fonction avec un petit circuit combinatoire. Bien sûr, il faut aussi prendre en compte le cas où ce registre déborde, ce qui demande d'ajouter des circuits pour gérer la situation.
[[File:Désambiguïsation des fenêtres de registres.png|centre|vignette|upright=2|Désambiguïsation des fenêtres de registres.]]
==L'unité mémoire==
L''''interface avec la mémoire''' est, comme son nom l'indique, des circuits qui servent d'intermédiaire entre le bus mémoire et le processeur. Elle est parfois appelée l'unité mémoire, l'unité d'accès mémoire, la ''load-store unit'', et j'en oublie. Nous utiliserons le terme d''''unité mémoire''', au même titre qu'on utilise le terme d'unité de calcul.
[[File:Unité de communication avec la mémoire, de type simple port.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type simple port.]]
Sur certains processeurs, elle gère les mémoires multiport.
[[File:Unité de communication avec la mémoire, de type multiport.png|centre|vignette|upright=2|Unité de communication avec la mémoire, de type multiport.]]
===Les registres d'interfaçage mémoire===
L'interface mémoire se résume le plus souvent à des '''registres d’interfaçage mémoire''', intercalés entre le bus mémoire et le chemin de données. Généralement, il y a au moins deux registres d’interfaçage mémoire : un registre relié au bus d'adresse, et autre relié au bus de données.
[[File:Registres d’interfaçage mémoire.png|centre|vignette|upright=2|Registres d’interfaçage mémoire.]]
Au lieu de lire ou écrire directement sur le bus, le processeur lit ou écrit dans ces registres, alors que l'unité mémoire s'occupe des échanges entre registres et bus mémoire. Lors d'une écriture, le processeur place l'adresse dans le registre d'interfaçage d'adresse, met la donnée à écrire dans le registre d'interfaçage de donnée, puis laisse l'unité d'accès mémoire faire son travail. Lors d'une lecture, il place l'adresse à lire sur le registre d'interfaçage d'adresse, il attend que la donnée soit lue, puis récupère la donnée dans le registre d'interfaçage de données.
L'avantage est que le processeur n'a pas à maintenir une donnée/adresse sur le bus durant tout un accès mémoire. Par exemple, prenons le cas où la mémoire met 15 cycles processeurs pour faire une lecture ou une écriture. Sans registres d'interfaçage mémoire, le processeur doit maintenir l'adresse durant 15 cycles, et aussi la donnée dans le cas d'une écriture. Avec ces registres, le processeur écrit dans les registres d'interfaçage mémoire au premier cycle, et passe les 14 cycles suivants à faire quelque chose d'autre. Par exemple, il faut faire un calcul en parallèle, envoyer des signaux de commande au banc de registre pour qu'il soit prêt une fois la donnée lue arrivée, etc. Cet avantage simplifie l'implémentation de certains modes d'adressage, comme on le verra à la fin du chapitre.
Les registres d’interfaçage peuvent être adressables, même si c'était extrêmement rare et que ca n'a été utilisé que sur de vieux ''mainframes''. Un exemple est celui du Burroughs B1700, qui expose deux registres d’interfaçage pour les données et un registre pour le bus d'adresse. Ils sont tous les trois adressables, ce qui fait que le processeur peut lire ou écrire dans ces registres avec une instruction MOV. Ils sont appelés : READ pour la donnée lue depuis la mémoire RAM, WRITE pour la donnée à écrire lors d'une écriture, MAR pour le bus d'adresse.
Une lecture/écriture était ainsi réalisée en plusieurs étapes, le séquenceur ne se souciait pas d'implémenter les instructions LOAD/STORE en plusieurs micro-opérations. Il fallait tout faire à la main, avec des instructions machines. Pour une lecture, il fallait placer l'adresse dans le registre MAR, puis exécuter une instruction LOAD, et récupérer la donnée lue dans le registre READ. Cela prenait une instruction MOV, suivie d'une instruction LOAD, elle-même suivie par une instruction MOV. avec un second MOV. Pour une écriture, il fallait placer la donnée à écrire dans le registre WRITE, puis placer l'adresse dans le registre MAR, et lancer une instruction WRITE. Le tout prenait donc deux MOV et une instruction WRITE.
===La gestion de l'alignement et du boutisme===
L'unité mémoire gère les accès mémoire non-alignés, à cheval sur deux mots mémoire (rappelez-vous le chapitre sur l'alignement mémoire). Dans le cas où les accès non-alignés sont interdits, elle lève une exception matérielle. Dans le cas où ils sont autorisés, elle les gère automatiquement, à savoir qu'elle charge deux mots mémoire et les combine entre eux pour donner le résultat final. Dans les deux cas, cela demande d'ajouter des circuits de détection des accès non-alignés, et éventuellement des circuits pour la double lecture/écriture.
Les circuits de détection des accès non-alignés sont très simples. Dans le cas où les adresses sont alignées sur une puissance de deux (cas le plus courant), il suffit de vérifier les bits de poids faible de l'adresse à lire. Prenons l'exemple d'un processeur avec des adresses codées sur 64 bits, avec des mots mémoire de 32 bits, alignés sur 32 bits (4 octets). Un mot mémoire contient 4 octets, les contraintes d'alignement font que les adresses autorisées sont des multiples de 4. En conséquence, les 2 bits de poids faible d'une adresse valide sont censés être à 0. En vérifiant la valeur de ces deux bits, on détecte facilement les accès non-alignés.
En clair, détecter les accès non-alignés demande de tester si les bits de poids faibles adéquats sont à 0. Il suffit donc d'un circuit de comparaison avec zéro; qui est une simple porte OU. Cette porte OU génère un bit qui indique si l'accès testé est aligné ou non : 1 si l'accès est non-aligné, 0 sinon. Le signal peut être transmis au séquenceur pour générer une exception matérielle, ou utilisé dans l'unité d'accès mémoire pour la double lecture/écriture.
La gestion automatique des accès non-alignés est plus complexe. Dans ce cas, l'unité mémoire charge deux mots mémoire et les combine entre eux pour donner le résultat final. Charger deux mots mémoires consécutifs est assez simple, si le registre d'interfaçage est un compteur. L'accès initial charge le premier mot mémoire, puis l'adresse stockée dans le registre d'interfaçage est incrémentée pour démarrer un second accès. Le circuit pour combiner deux mots mémoire contient des registres, des circuits de décalage, des multiplexeurs.
===L'unité de calcul d'adresse===
Les registres d'interfaçage sont presque toujours présents, mais le circuit que nous allons voir est complétement facultatif. Il s'agit d'une unité de calcul spécialisée dans les calculs d'adresse, dont nous avons parlé rapidement dans la section sur les ALU. Elle s'appelle l''''''Address generation unit''''', ou AGU. Elle est parfois séparée de l'interface mémoire proprement dit, et est alors considérée comme une unité de calcul à part.
: Rappelons que dans ce chapitre, ainsi que dans les quatre chapitres suivants, nous ne parlons que des architectures à registres généraux. Aussi, nous mettons de côté les anciens processeurs à accumulateur et les CPU 8 bits, commercialisés avant l'arrivée des registres généraux, ainsi que certaines architectures non-conventionnelles comme les DSPs, qui ont des unités de calcul d'adresse dédiées. Mais nous ne parlerons pas de celles-ci dans ce chapitre.
L'AGU est utilisée pour certains modes d'adressage, à savoir ceux qui demandent de combiner une adresse avec soit un indice, soit un décalage, plus rarement les deux. Les calculs d'adresse prennent une adresse de base, et lui ajoutent un indice ou un décalage. Il faut parfois décaler l'indice, de quelques rangs. Pas besoin de multiplications, de divisions, ou d'autres opération plus complexe : décalages et additions/soustractions suffisent. L'AGU est donc beaucoup plus simple qu'une ALU normale et se résume à un additionneur-soustracteur couplé à un décaleur. Son cout en circuits est ridicule.
Mais pourquoi ajouter une AGU, alors que l'ALU entière suffit largement pour implémenter les modes d'adressage complexes. Il est parfaitement possible de faire les calculs d'adresse dans l'ALU entière, puis d'envoyer le résultat à l'unité mémoire. Sauf que faire ainsi pose plusieurs problèmes. Premièrement, cela complexifie le travail du séquenceur, qui doit enchainer deux micro-opérations. Ensuite, cela demande de câbler un chemin reliant la sortie de l'ALU entière à l'unité mémoire. Rien d'impossible, on verra même cela en détail à la fin du chapitre. Mais cette connexion est souvent difficile à câbler.
En ajoutant une AGU, les deux défauts disparaissent. La liaison entre ALU et unité mémoire disparait, l'unité mémoire n'est reliée qu'aux registres. Le séquenceur est aussi simplifié, vu que les accès mémoire ne font plus qu'une seule micro-opération, il n'y a plus besoin d'utiliser une micro-opération pour les calculs d'adresse. Le câblage du processeur est simplifié, au prix d'un cout modéré en transistors. On peut même obtenir nune économie, les simplifications du séquenceur sur-compensant le cout en transistors de l'AGU.
Parlons rapidement de l'implémentation du mode d'adressage "Base + Indice + Décalage", qui additionne une adresse de base, un indice (mis à l'échelle) et un décalage (une constante immédiate). Il demande d'additionner trois opérandes, chose que l'ALU entière ne sait pas faire (sauf exceptions). En théorie, une AGU peut réaliser ce calcul sans cout, en utilisant un additionneur ''carry save'', capable d'additionner trois opérandes à la fois. L'AGU est alors implémentée avec un circuit décaleur, pour décaler l'indice, et un additionneur trois-opérandes. Le résultat est que l'on peut faire les calculs d'adresse en un seul cycle d'horloge, pour un cout en hardware modéré.
[[File:Unité de calcul d'adresse.png|centre|vignette|upright=2|Unité de calcul d'adresse]]
Chose étrange, ce n'est pas ce qui était fait sur les premiers processeurs Intel. Sur ces CPUs, le calcul de l'adresse se faisait en deux cycles d'horloge. Lors du premier cycle d'horloge, l'ALU entière faisait le calcul d'adresse "Base + Indice". Lors du second cycle, le résultat de l'addition précédente est additionné avec le décalage, dans l'AGU. Pour cela, l'AGU était reliée à l'ALU entière et à l'unité de contrôle. Difficile de comprendre cette décision, l'implémentation ne profitant que partiellement des avantages mentionnés plus haut pour ce mode d'adressage.
===Le rafraichissement mémoire optimisé et le contrôleur mémoire intégré===
Depuis les années 80, les processeurs sont souvent combinés avec une mémoire principale de type DRAM. De telles mémoires doivent être rafraichies régulièrement pour ne pas perdre de données. Le rafraichissement se fait généralement adresse par adresse, ou ligne par ligne (les lignes sont des super-bytes internes à la DRAM). Le rafraichissement est en théorie géré par le contrôleur mémoire installé sur la carte mère. Mais au tout début de l'informatique, du temps des processeurs 8 bits, le rafraichissement mémoire était géré directement par le processeur.
Divers processeurs implémentaient de quoi faciliter le rafraichissement par adresse. Par exemple, le processeur 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''. Et tout cela était intégré à l'unité d'accès mémoire.
Depuis les années 2000, les processeurs modernes ont un contrôleur mémoire DRAM intégré directement dans le processeur. Ce qui fait qu'ils gèrent non seulement le rafraichissement, mais aussi d'autres fonctions bien pus complexes.
===L'interface de l'unité mémoire===
Vu de l'extérieur, l'unité mémoire ressemble à n'importe quel circuit électronique, avec des entrées et des sorties. L'unité mémoire est généralement multiport, avec un port d'entrée et un port de sortie. Le port d'entrée est là où on envoie l'adresse à lire/écrire, ainsi que la donnée à écrire pour les écritures. Si l'unité mémoire incorpore une AGU, on envoie aussi les indices et autres données sur le port d'entrée. Le port de sortie est utilisé pour récupérer le résultat des lectures. Une unité mémoire est donc reliée au chemin de données via deux entrées et une sortie : une entrée d'adresse, une entrée de données, et une sortie pour les données lues.
L'unité mémoire est connectée au reste du processeur grâce à un réseau d'interconnexion qu'on étudiera plus loin. Les connexions principales sont celles avec les registres : les adresses à lire/écrire sont souvent lues depuis les registres, les lectures copient une donnée dans un registre. Il peut y avoir une connexion avec l'unité de calcul pour les opérations ''load-up'', ou pour le calcul d'adresse, mais c'est secondaire.
[[File:Unité d'accès mémoire LOAD-STORE.png|centre|vignette|upright=2|Unité d'accès mémoire LOAD-STORE.]]
===Un exemple : l'unité mémoire du 8086 et ses dérivés===
Après avoir vu la théorie, voyons un exemple du monde réel. Nous allons étudier l'exemple du processeur Intel 8086 et de son dérivé, le 80186. Ces processeurs avaient une particularité : leur unité mémoire était fortement séparée du reste du processeur. Le processeur était séparé en une ''Bus Interface Unit'' et une ''Execution Unit''. L'''Execution Unit'' a un nom trompeur : c'est l'ensemble du processeur, unité mémoire exclue. La ''Bus Interface Unit'' est l'unité mémoire. Elle se charge à la fois du chargement des données et des instructions.
Cependant, nous n'allons pouvoir détailler cette unité dans le détail, car nous n'avons pas encore vu la mémoire virtuelle, ni le préchargement. Mais cela ne nous gênera pas, vous aurez simplement quelques détails passés sous silence pour ce qui est de l'unité de calcul. Toujours est-il que voici à quoi ressemble l'intérieur d'un 80186. Elle contient :
* des registres d'adresse utilisés pour ses fonctionnalités de segmentation ;
* une unité de calcul d'adresse utilisée pour les calculs d'adresse ;
* une interface avec le bus mémoire, qu'on détaillera plus bas ;
* une mémoire FIFO où les instructions sont chargées en avance, appelée la ''Prefetch Input Queue''.
[[File:80186 arch.png|centre|vignette|upright=2.5|Microarchitecture du 80186.]]
Nous détaillerons la ''Prefetch Input Queue'' dans le chapitre sur le préchargement. Les registres et l'unité de calcul seront détaillés dans le chapitre sur la mémoire virtuelle. Il est intéressant de regarder l'interface avec le bus mémoire. Cette interface contient les registres d’interfaçage, ainsi que des circuits annexes.
Pour comprendre ce qu'elle fait, il faut savoir que le processeur communique avec la mémoire avec une séquence très précise, qui dure exactement quatre cycles. Les quatre cycles sont appelés des ''T-states'' et ils sont numérotés de 1 à 4 : T1, T2, T3 et T4.
Pour une lecture, l'adresse est envoyée lors du premier cycle, elle est disponible sur le bus au troisième cycle. Les cycles 1 et 3 sont inutilisés, le processeur se déconnecte du bus lors de ces cycles.
{|class="wikitable"
|-
! T1 !! T2 !! T3 !! T4
|-
| Adresse || || Donnée lue ||
|}
Pour une lecture, l'adresse est envoyée lors du premier cycle, la donnée est envoyée sur le bus lors des trois cycles suivants.
{|class="wikitable"
! T1 !! T2 !! T3 !! T4
|-
| Adresse || colspan="3 | Donnée à écrire
|}
Les ''timings'' vus plus haut ne sont effectivement observés que si la mémoire est assez rapide. Mais le 8086 pouvait être combiné avec des mémoires RAM assez diverses, certaines plus lentes que d'autres. Si la mémoire est trop lente, elle peut prendre plusieurs cycles avant de renvoyer la donnée lue ou d'accepter la donnée à écrire. Pour gérer la situation, le processeur pouvait attendre que la mémoire réponde, avec des ''wait states''.
Pour cela, la mémoire prévenait quand la donnée lue était disponible, ou quand une écriture était terminée. Le processeur avait une entrée READY qui indiquait que la mémoire était disponible ou qu'elle avait terminé son travail. Lors d'une lecture, la mémoire mettait cette entrée READY à 1 quand la donnée lue était disponible sur le bus mémoire. Idem pour une écriture. L'implémentation dans l'unité mémoire est très simple : le processeur restait dans le cycle T3 en attendant que l'entrée READY soit à 1. Lors d'une écriture, cela maintenait à donnée à écrire sur le bus mémoire. Pour une lecture, cela permettait de laisser le registre d’interfaçage des données connecté au bus mémoire, en attendant la donnée lue.
Nous venons de voir que le processeur pouvait rester dans l'état T3 si le besoin s'en fait sentir. Mais cette astuce est utilisée dans d'autres situations. Par exemple, le processeur dispose d'une instruction HALT, qui le déconnecte totalement du bus. Le processeur reste déconnecté tant qu'il ne reçoit pas un RESET ou une interruption. Il s'agit d'une forme limitée de mise en veille, qui n'utilisait pas de méthodes pour économiser de l'énergie. L'instruction HALT était implémentée en restant en permanence dans l'état T2. Le processeur attendait d'avoir terminé toute instruction précédente, puis lançait une pseudo-lecture et restait en état T1 en attendant une interruption ou un RESET.
Le registre d’interfaçage pour les adresses est connecté sur le bus mémoire lors du premier cycle, celui pour les données l'est à des cycles différents suivant l'opération demandée. La connexion/déconnexion des registres d’interfaçage se fait en utilisant des circuits trois-états. Il y a une couche de circuits trois-états entre le registre d’interfaçage et le bus mémoire, qui permet de connecter/déconnecter les registres du bus mémoire, mais aussi d'imposer le sens de transfert suivant qu'on fasse une lecture ou une écriture.
[[File:Circuit d'interfacage mémoire du 8086.png|centre|vignette|upright=2|Circuit d’interfaçage mémoire du 8086.]]
Le circuit en question est un circuit séquentiel assez simple, une petite "machine à état" comme on le dit en termes techniques. Le circuit séquentiel commande les circuits trois-états. Une implémentation naïve serait d'utiliser un compteur pouvant compter de 0 à 3, avec des comparateurs. Il faut, en tout :
* Un comparateur qui détermine si le compteur est au cycle 1. Si c'est le cas, il connecte le registre d’interfaçage d'adresse sur le bus mémoire.
* Un autre comparateur détermine si une lecture est en cours et que l'on est dans le troisième cycle. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode lecture.
* Un autre comparateur détermine si une écriture est en cours et que l'on est dans les cycles adéquats. Si c'est le cas, il configure les circuits trois-états du registre d’interfaçage de données, en mode écriture.
Afin de simplifier fortement le circuit, le compteur utilisé utilise la représentation ''one hot''. Pour rappel, cela veut dire qu'il y a une bascule pour chaque valeur que peut prendre le compteur. Le compteur ''one hot'' est composé de quatre bascules. La première bascule est à 1 pour le cycle T1, la seconde est à 1 pour le cycle T2, la troisième est à 1 pour le cycle T3, la quatrième l'est pour le cycle T4. Ainsi, on peut se passer de certains comparateurs. Par exemple, le comparateur pour l'adresse disparait : la bascule commande directement les circuits trois-états. Les deux autres comparateurs se transforment des circuits à respectivement une ou quatre portes logiques.
Incrémenter le compteur se fait cependant sous certaines conditions. La machine à état contient des circuits qui décident quand incrémenter le compteur. Pour cela, il y a un circuit qui détermine quand on peut passer du cycle T1 au cycle T2, un autre pour le passage de T2 à T3, etc. Les circuits en question sont un paquet de portes logiques assez simple. Ils reçoivent des signaux de commande provenant du séquenceur, ainsi que des entrées comme l'entrée READY pour les ''wait states''.
[[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|centre|vignette|upright=2|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]]
Maintenant, parlons des problématiques liées au calcul d'adresse. Tous les accès mémoire demandent de faire un calcul d'adresse, en raison de l'usage de la fonctionnalité de segmentation. Et ce calcul d'adresse prend deux cycles. L'adresse est calculée, puis que l'accès mémoire est réalisé. En tout, cela fait 6 cycles : 2 pour le calcul d'adresse, puis les cycles T1, T2, T3 et T4.
Une optimisation importante est que deux accès mémoire peuvent se recouvrir. A savoir que pendant qu'un accès mémoire a lieu sur le bus mémoire, on calcule l'adresse de l'accès du suivant. Les deux ont lieu dans des circuits séparés, ce qui fait que c'est possible. Pour donner un exemple, on peut calculer l'adresse de la prochaine instruction, pendant que l'instruction mémoire en cours est exécutée. Le séquenceur du processeur est configuré pour démarrer le calcul d'adresse au bon moment. Il attend que le compteur soit dans l'état T3 et T4.
{|class="wikitable"
|-
! Unité de calcul
| class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || || || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || || || ||
|-
! Bus mémoire
| || || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_vert" | Instruction 1 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2 || class="f_rouge" | Instruction 2
|}
: La machine à état est décrite en détail dans cet article de blog : [https://www.righto.com/2024/04/intel-8088-bus-state-machine.html Talking to memory: Inside the Intel 8088 processor's bus interface state machine ].
==Le chemin de données et son réseau d'interconnexions==
Nous venons de voir que le chemin de données contient une unité de calcul (parfois plusieurs), des registres isolés, un banc de registre, une unité mémoire. Le tout est chapeauté par une unité de contrôle qui commande le chemin de données, qui fera l'objet des prochains chapitres. Mais il faut maintenant relier registres, ALU et unité mémoire pour que l'ensemble fonctionne. Pour cela, diverses interconnexions internes au processeur se chargent de relier le tout.
Sur les anciens processeurs, les interconnexions sont assez simples et se résument à un ou deux '''bus internes au processeur''', reliés au bus mémoire. C'était la norme sur des architectures assez ancienne, qu'on n'a pas encore vu à ce point du cours, appelées les architectures à accumulateur et à pile. Mais ce n'est plus la solution utilisée actuellement. De nos jours, le réseaux d'interconnexion intra-processeur est un ensemble de connexions point à point entre ALU/registres/unité mémoire. Et paradoxalement, cela rend plus facile de comprendre ce réseau d'interconnexion.
===Introduction propédeutique : l'implémentation des modes d'adressage principaux===
L'organisation interne du processeur dépend fortement des modes d'adressage supportés. Pour simplifier les explications, nous allons séparer les modes d'adressage qui gèrent les pointeurs et les autres. Suivant que le processeur supporte les pointeurs ou non, l'organisation des bus interne est légèrement différente. La différence se voit sur les connexions avec le bus d'adresse et de données.
Tout processeur gère au minimum le '''mode d'adressage absolu''', où l'adresse est intégrée à l'instruction. Le séquenceur extrait l'adresse mémoire de l'instruction, et l'envoie sur le bus d'adresse. Pour cela, le séquenceur est relié au bus d'adresse, le chemin de donnée est relié au bus de données. Le chemin de donnée n'est pas connecté au bus d'adresse, il n'y a pas d'autres connexions.
[[File:Chemin de données sans support des pointeurs.png|centre|vignette|upright=2|Chemin de données sans support des pointeurs]]
Le '''support des pointeurs''' demande d'intégrer des modes d'adressage dédiés : l'adressage indirect à registre, l'adresse base + indice, et les autres. Les pointeurs sont stockés dans le banc de registre et sont modifiés par l'unité de calcul. Pour supporter les pointeurs, le chemin de données est connecté sur le bus d'adresse avec le séquenceur. Suivant le mode d'adressage, le bus d'adresse est relié soit au chemin de données, soit au séquenceur.
[[File:Chemin de données avec support des pointeurs.png|centre|vignette|upright=2|Chemin de données avec support des pointeurs]]
Pour terminer, il faut parler des instructions de '''copie mémoire vers mémoire''', qui copient une donnée d'une adresse mémoire vers une autre. Elles ne se passent pas vraiment dans le chemin de données, mais se passent purement au niveau des registres d’interfaçage. L'usage d'un registre d’interfaçage unique permet d'implémenter ces instructions très facilement. Elle se fait en deux étapes : on copie la donnée dans le registre d’interfaçage, on l'écrit en mémoire RAM. L'adresse envoyée sur le bus d'adresse n'est pas la même lors des deux étapes.
===Le banc de registre est multi-port, pour gérer nativement les opérations dyadiques===
Les architectures RISC et CISC incorporent un banc de registre, qui est connecté aux unités de calcul et au bus mémoire. Et ce banc de registre peut être mono-port ou multiport. S'il a existé d'anciennes architectures utilisant un banc de registre mono-port, elles sont actuellement obsolètes. Nous les aborderons dans un chapitre dédié aux architectures dites canoniques, mais nous pouvons les laisser de côté pour le moment. De nos jours, tous les processeurs utilisent un banc de registre multi-port.
[[File:Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres).png|centre|vignette|upright=2|Chemin de données minimal d'une architecture LOAD-STORE (sans MOV inter-registres)]]
Le banc de registre multiport est optimisé pour les opérations dyadiques. Il dispose précisément de deux ports de lecture et d'un port d'écriture pour l'écriture. Un port de lecture par opérande et le port d'écriture pour enregistrer le résultat. En clair, le processeur peut lire deux opérandes et écrire un résultat en un seul cycle d'horloge. L'avantage est que les opérations simples ne nécessitent qu'une micro-opération, pas plus.
[[File:ALU data paths.svg|centre|vignette|upright=1.5|Processeur LOAD-STORE avec un banc de registre multiport, avec les trois ports mis en évidence.]]
===Une architecture LOAD-STORE basique, avec adressage absolu===
Voyons maintenant comment l'implémentation d'une architecture RISC très simple, qui ne supporte pas les adressages pour les pointeurs, juste les adressages inhérent (à registres) et absolu (par adresse mémoire). Les instructions LOAD et STORE utilisent l'adressage absolu, géré par le séquenceur, reste à gérer l'échange entre banc de registres et bus de données. Une lecture LOAD relie le bus de données au port d'écriture du banc de registres, alors que l'écriture relie le bus au port de lecture du banc de registre. Pour cela, il faut ajouter des interconnexions, comme illustré par le schéma ci-dessous.
[[File:Bus interne au processeur sur archi LOAD STORE avec banc de registres multiport.png|centre|vignette|upright=2|Organisation interne d'une architecture LOAD STORE avec banc de registres multiport. Nous n'avons pas représenté les signaux de commandes envoyés par le séquenceur au chemin de données.]]
Ajoutons ensuite les instructions de copie entre registres, souvent appelées instruction COPY ou MOV. Elles existent sur la plupart des architectures LOAD-STORE. Une première solution boucle l'entrée du banc de registres sur son entrée, ce qui ne sert que pour les copies de registres.
[[File:Chemin de données d'une architecture LOAD-STORE.png|centre|vignette|upright=2|Chemin de données d'une architecture LOAD-STORE]]
Mais il existe une seconde solution, qui ne demande pas de modifier le chemin de données. Il est possible de faire passer les copies de données entre registres par l'ALU. Lors de ces copies, l'ALU une opération ''Pass through'', à savoir qu'elle recopie une des opérandes sur sa sortie. Le fait qu'une ALU puisse effectuer une opération ''Pass through'' permet de fortement simplifier le chemin de donnée, dans le sens où cela permet d'économiser des multiplexeurs. Mais nous verrons cela sous peu. D'ailleurs, dans la suite du chapitre, nous allons partir du principe que les copies entre registres passent par l'ALU, afin de simplifier les schémas.
===L'ajout des modes d'adressage indirects à registre pour les pointeurs===
Passons maintenant à l'implémentation des modes d'adressages pour les pointeurs. Avec eux, l'adresse mémoire à lire/écrire n'est pas intégrée dans une instruction, mais est soit dans un registre, soit calculée par l'ALU.
Le premier mode d'adressage de ce type est le mode d'adressage indirect à registre, où l'adresse à lire/écrire est dans un registre. L'implémenter demande donc de connecter la sortie du banc de registres au bus d'adresse. Il suffit d'ajouter un MUX en sortie d'un port de lecture.
[[File:Chemin de données à trois bus.png|centre|vignette|upright=2|Chemin de données à trois bus.]]
Le mode d'adressage base + indice est un mode d'adressage où l'adresse à lire/écrire est calculée à partir d'une adresse et d'un indice, tous deux présents dans un registre. Le calcul de l'adresse peut être fait soit dans l'ALU entière, soit dans une AGU dédiée.
* Si l'unité mémoire intègre une AGU, il suffit de relier l'unité mémoire aux deux ports de lecture du banc de registre. Le schéma du dessous montre ce qu'il en est avec un calcul d'adresse fait dans l'ALU entière.
* S'il est réalisé dans l'ALU entière, on doit connecter la sortie de l'unité de calcul au bus d'adresse. L'instruction mémoire se fait alors en deux étapes : un calcul d'adresse, l'accès mémoire. Il est possible d'ajouter un registre sur cette connexion, afin de simplifier l'implémentation, le registre étant alors peu ou prou le registre d’interfaçage d'adresse. L'unité mémoire lit l'adresse à lire dans le registre intercalé entre l'ALU entière et l'unité mémoire.
[[File:Bus avec adressage base+index.png|centre|vignette|upright=2|Bus avec adressage base+index]]
Le chemin de données précédent gère aussi le mode d'adressage indirect avec pré-décrément. Pour rappel, ce mode d'adressage est une variante du mode d'adressage indirect, qui utilise une pointeur/adresse stocké dans un registre. La différence est que ce pointeur est décrémenté avant d'être envoyé sur le bus d'adresse. L'implémentation matérielle est la même que pour le mode Base + Indice : l'adresse est lue depuis les registres, décrémentée dans l'ALU, et envoyée sur le bus d'adresse.
Le schéma précédent montre que le bus d'adresse est connecté à un MUX avant l'ALU et un autre MUX après. Mais il est possible de se passer du premier MUX, utilisé pour le mode d'adressage indirect à registre. La condition est que l'ALU supporte l'opération ''pass through'', un NOP, qui recopie une opérande sur sa sortie. L'ALU fera une opération NOP pour le mode d'adressage indirect à registre, un calcul d'adresse pour le mode d'adressage base + indice. Par contre, faire ainsi rendra l'adressage indirect légèrement plus lent, vu que le temps de passage dans l'ALU sera compté.
[[File:Bus avec adressage indirect.png|centre|vignette|upright=2|Bus avec adressages pour les pointeurs, simplifié.]]
Dans ce qui va suivre, nous allons partir du principe que le processeur est implémenté en suivant le schéma précédent, afin d'avoir des schéma plus lisibles.
===L'adressage immédiat et les modes d'adressages exotiques===
Passons maintenant au mode d’adressage immédiat, qui permet de préciser une constante dans une instruction directement. La constante est extraite de l'instruction par le séquenceur, puis insérée au bon endroit dans le chemin de données. Pour les opérations arithmétiques/logiques/branchements, il faut insérer la constante extraite sur l'entrée de l'ALU. Sur certains processeurs, la constante peut être négative et doit alors subir une extension de signe dans un circuit spécialisé.
[[File:Chemin de données - Adressage immédiat avec extension de signe.png|centre|vignette|upright=2|Chemin de données - Adressage immédiat avec extension de signe.]]
L'implémentation précédente gère aussi les modes d'adressage base + décalage et absolu indexé. Pour rappel, le premier ajoute une constante à une adresse prise dans les registres, le second prend une adresse constante et lui ajoute un indice pris dans les registres. Dans les deux cas, on lit un registre, extrait une constante/adresse de l’instruction, additionne les deux dans l'ALU, avant d'envoyer le résultat sur le bus d'adresse. La seule difficulté est de désactiver l'extension de signe pour les adresses.
Le mode d'adressage absolu peut être traité de la même manière, si l'ALU est capable de faire des NOPs. L'adresse est insérée au même endroit que pour le mode d'adressage immédiat, parcours l'unité de calcul inchangée parce que NOP, et termine sur le bus d'adresse.
[[File:Chemin de données avec une ALU capable de faire des NOP.png|centre|vignette|upright=2|Chemin de données avec adressage immédiat étendu pour gérer des adresses.]]
Passons maintenant au cas particulier d'une instruction MOV qui copie une constante dans un registre. Il n'y a rien à faire si l'unité de calcul est capable d'effectuer une opération NOP/''pass through''. Pour charger une constante dans un registre, l'ALU est configurée pour faire un NOP, la constante traverse l'ALU et se retrouve dans les registres. Si l'ALU ne gère pas les NOP, la constante doit être envoyée sur l'entrée d'écriture du banc de registres, à travers un MUX dédié.
[[File:Implémentation de l'adressage immédiat dans le chemin de données.png|centre|vignette|upright=2|Implémentation de l'adressage immédiat dans le chemin de données]]
===Les architectures CISC : les opérations ''load-op''===
Tout ce qu'on a vu précédemment porte sur les processeurs de type LOAD-STORE, souvent confondus avec les processeurs de type RISC, où les accès mémoire sont séparés des instructions utilisant l'ALU. Il est maintenant temps de voir les processeurs CISC, qui gèrent des instructions ''load-op'', qui peuvent lire une opérande depuis la mémoire.
L'implémentation des opérations ''load-op'' relie le bus de donnée directement sur une entrée de l'unité de calcul, en utilisant encore une fois un multiplexeur. L'implémentation parait simple, mais c'est parce que toute la complexité est déportée dans le séquenceur. C'est lui qui se charge de détecter quand la lecture de l'opérande est terminée, quand l'opérande est disponible.
Les instructions ''load-op'' s'exécutent en plusieurs étapes, en plusieurs micro-opérations. Il y a typiquement une étape pour l'opérande à lire en mémoire et une étape de calcul. L'usage d'un registre d’interfaçage permet d'implémenter les instructions ''load-op'' très facilement. Une opération ''load-op'' charge l'opérande en mémoire dans un registre d’interfaçage, puis relier ce registre d’interfaçage sur une des entrées de l'ALU. Un simple multiplexeur suffit pour implémenter le tout, en plus des modifications adéquates du séquenceur.
[[File:Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire.png|centre|vignette|upright=2|Chemin de données d'un CPU CISC avec lecture des opérandes en mémoire]]
Supporter les instructions multi-accès (qui font plusieurs accès mémoire) ne modifie pas fondamentalement le réseau d'interconnexion, ni le chemin de données La raison est que supporter les instructions multi-accès se fait au niveau du séquenceur. En réalité, les accès mémoire se font en série, l'un après l'autre, sous la commande du séquenceur qui émet plusieurs micro-opérations mémoire consécutives. Les données lues sont placées dans des registres d’interactivement mémoire, ce qui demande d'ajouter des registres d’interfaçage mémoire en plus.
==Annexe : le cas particulier du pointeur de pile==
Le pointeur de pile est un registre un peu particulier. Il peut être placé dans le chemin de données ou dans le séquenceur, voire dans l'unité de chargement, tout dépend du processeur. Tout dépend de si le pointeur de pile gère une pile d'adresses de retour ou une pile d'appel.
===Le pointeur de pile non-adressable explicitement===
Avec une pile d'adresse de retour, le pointeur de pile n'est pas adressable explicitement, il est juste adressé implicitement par des instructions d'appel de fonction CALL et des instructions de retour de fonction RET. Le pointeur de pile est alors juste incrémenté ou décrémenté par un pas constant, il ne subit pas d'autres opérations, son adressage est implicite. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Il n'y a pas besoin de le relier au chemin de données, vu qu'il n'échange pas de données avec les autres registres. Il y a alors plusieurs solutions, mais la plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Quelques processeurs simples disposent d'une pile d'appel très limitée, où le pointeur de pile n'est pas adressable explicitement. Il est adressé implicitement par les instruction CALL, RET, mais aussi PUSH et POP, mais aucune autre instruction ne permet cela. Là encore, le pointeur de pile ne communique pas avec les autres registres. Il est juste incrémenté/décrémenté par pas constants, qui sont fournis par le séquenceur. Là encore, le plus simple est de placer le pointeur de pile dans le séquenceur et de l'incrémenter par un incrémenteur dédié.
Dans les deux cas, le pointeur de pile est placé dans l'unité de contrôle, le séquenceur, et est associé à un incrémenteur dédié. Il se trouve que cet incrémenteur est souvent partagé avec le ''program counter''. En effet, les deux sont des adresses mémoire, qui sont incrémentées et décrémentées par pas constants, ne subissent pas d'autres opérations (si ce n'est des branchements, mais passons). Les ressemblances sont suffisantes pour fusionner les deux circuits. Ils peuvent donc avoir un '''incrémenteur partagé'''.
L'incrémenteur en question est donc partagé entre pointeur de pile, ''program counter'' et quelques autres registres similaires. Par exemple, le Z80 intégrait un registre pour le rafraichissement mémoire, qui était réalisé par le CPU à l'époque. Ce registre contenait la prochaine adresse mémoire à rafraichir, et était incrémenté à chaque rafraichissement d'une adresse. Et il était lui aussi intégré au séquenceur et incrémenté par l'incrémenteur partagé.
[[File:Organisation interne d'une architecture à pile.png|centre|vignette|upright=2|Organisation interne d'une architecture à pile]]
===Le pointeur de pile adressable explicitement===
Maintenant, étudions le cas d'une pile d'appel, précisément d'une pile d'appel avec des cadres de pile de taille variable. Sous ces conditions, le pointeur de pile est un registre adressable, avec un nom/numéro de registre dédié. Tel est par exemple le cas des processeurs x86 avec le registre ESP (''Extended Stack Pointer''). 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 calculs d'adresse, afin de lire/écrire des variables locales, les arguments d'une fonction, et autres.
Dans ce cas, la meilleure solution est de placer le pointeur de pile dans le banc de registre généraux, avec les autres registres entiers. En faisant cela, la manipulation du pointeur de pile est faite par l'unité de calcul entière, pas besoin d'utiliser un incrémenteur dédiée. Il a existé des processeurs qui mettaient le pointeur de pile dans le banc de registre, mais l'incrémentaient avec un incrémenteur dédié, mais nous les verrons dans le chapitre sur les architectures à accumulateur. La raison est que sur les processeurs concernés, les adresses ne faisaient pas la même taille que les données : c'était des processeurs 8 bits, qui géraient des adresses de 16 bits.
==Annexe : l'implémentation du système d'''aliasing'' des registres des CPU x86==
Il y a quelques chapitres, nous avions parlé du système d'''aliasing'' des registres des CPU x86. Pour rappel, il permet de donner plusieurs noms de registre pour un même registre. Plus précisément, pour un registre 64 bits, le registre complet aura un nom de registre, les 32 bits de poids faible auront leur nom de registre dédié, idem pour les 16 bits de poids faible, etc. Il est possible de faire des calculs sur ces moitiés/quarts/huitièmes de registres sans problème.
===L'''aliasing'' du 8086, pour les registres 16 bits===
[[File:Register 8086.PNG|vignette|Register 8086]]
L'implémentation de l'''aliasing'' est apparue sur les premiers CPU Intel 16 bits, notamment le 8086. En tout, ils avaient quatre registres généraux 16 bits : AX, BX, CX et DX. Ces quatre registres 16 bits étaient coupés en deux octets, chacun adressable. Par exemple, le registre AX était coupé en deux octets nommés AH et AL, chacun ayant son propre nom/numéro de registre. Les instructions d'addition/soustraction pouvaient manipuler le registre AL, ou le registre AH, ce qui modifiait les 8 bits de poids faible ou fort selon le registre choisit.
Le banc de registre ne gére que 4 registres de 16 bits, à savoir AX, BX, CX et DX. Lors d'une lecture d'un registre 8 bits, le registre 16 bit entier est lu depuis le banc de registre, mais les bits inutiles sont ignorés. Par contre, l'écriture peut se faire soit avec 16 bits d'un coup, soit pour seulement un octet. Le port d'écriture du banc de registre peut être configuré de manière à autoriser l'écriture soit sur les 16 bits du registre, soit seulement sur les 8 bits de poids faible, soit écrire dans les 8 bits de poids fort.
[[File:Port d'écriture du banc de registre du 8086.png|centre|vignette|upright=2.5|Port d'écriture du banc de registre du 8086]]
Une opération sur un registre 8 bits se passe comme suit. Premièrement, on lit le registre 16 bits complet depuis le banc de registre. Si l'on a sélectionné l'octet de poids faible, il ne se passe rien de particulier, l'opérande 16 bits est envoyée directement à l'ALU. Mais si on a sélectionné l'octet de poids fort, la valeur lue est décalée de 7 rangs pour atterrir dans les 8 octets de poids faible. Ensuite, l'unité de calcul fait un calcul avec cet opérande, un calcul 16 bits tout ce qu'il y a de plus classique. Troisièmement, le résultat est enregistré dans le banc de registre, en le configurant convenablement. La configuration précise s'il faut enregistrer le résultat dans un registre 16 bits, soit seulement dans l'octet de poids faible/fort.
Afin de simplifier le câblage, les 16 bits des registres AX/BX/CX/DX sont entrelacés d'une manière un peu particulière. Intuitivement, on s'attend à ce que les bits soient physiquement dans le même ordre que dans le registre : le bit 0 est placé à côté du bit 1, suivi par le bit 2, etc. Mais à la place, l'octet de poids fort et de poids faible sont mélangés. Deux bits consécutifs appartiennent à deux octets différents. Le tout est décrit dans le tableau ci-dessous.
{|class="wikitable"
|-
! Registre 16 bits normal
| class="f_bleu" | 15
| class="f_bleu" | 14
| class="f_bleu" | 13
| class="f_bleu" | 12
| class="f_bleu" | 11
| class="f_bleu" | 10
| class="f_bleu" | 9
| class="f_bleu" | 8
| class="f_rouge" | 7
| class="f_rouge" | 6
| class="f_rouge" | 5
| class="f_rouge" | 4
| class="f_rouge" | 3
| class="f_rouge" | 2
| class="f_rouge" | 1
| class="f_rouge" | 0
|-
! Registre 16 bits du 8086
| class="f_bleu" | 15
| class="f_rouge" | 7
| class="f_bleu" | 14
| class="f_rouge" | 6
| class="f_bleu" | 13
| class="f_rouge" | 5
| class="f_bleu" | 12
| class="f_rouge" | 4
| class="f_bleu" | 11
| class="f_rouge" | 3
| class="f_bleu" | 10
| class="f_rouge" | 2
| class="f_bleu" | 9
| class="f_rouge" | 1
| class="f_bleu" | 8
| class="f_rouge" | 0
|}
En faisant cela, le décaleur en entrée de l'ALU est bien plus simple. Il y a 8 multiplexeurs, mais le câblage est bien plus simple. Par contre, en sortie de l'ALU, il faut remettre les bits du résultat dans l'ordre adéquat, celui du registre 8086. Pour cela, les interconnexions sur le port d'écriture sont conçues pour. Il faut juste mettre les fils de sortie de l'ALU sur la bonne entrée, par besoin de multiplexeurs.
===L'''aliasing'' sur les processeurs x86 32/64 bits===
Les processeurs x86 32 et 64 bits ont un système d'''aliasing'' qui complète le système précédent. Les processeurs 32 bits étendent les registres 16 bits existants à 32 bits. Pour ce faire, le registre 32 bit a un nouveau nom de registre, distincts du nom de registre utilisé pour l'ancien registre 16 bits. Il est possible d'adresser les 16 bits de poids faible de ce registre, avec le même nom de registre que celui utilisé pour le registre 16 sur les processeurs d'avant. Même chose avec les processeurs 64, avec l'ajout d'un nouveau nom de registre pour adresser un registre de 64 bit complet.
En soit, implémenter ce système n'est pas compliqué. Prenons le cas du registre RAX (64 bits), et de ses subdivisions nommées EAX (32 bits), AX (16 bits). À l'intérieur du banc de registre, il n'y a que le registre RAX. Le banc de registre ne comprend qu'un seul nom de registre : RAX. Les subdivisions EAX et AX n'existent qu'au niveau de l'écriture dans le banc de registre. L'écriture dans le banc de registre est configurable, de manière à ne modifier que les bits adéquats. Le résultat d'un calcul de l'ALU fait 64 bits, il est envoyé sur le port d'écriture. À ce niveau, soit les 64 bits sont écrits dans le registre, soit seulement les 32/16 bits de poids faible. Le système du 8086 est préservé pour les écritures dans les 16 bits de poids faible.
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Les composants d'un processeur
| prevText=Les composants d'un processeur
| next=L'unité de chargement et le program counter
| nextText=L'unité de chargement et le program counter
}}
</noinclude>
9zpe4oqt1zpqxoqensgt8444luxj3fi
Mathc initiation/a10
0
76338
772069
771954
2026-09-12T09:18:22Z
Xhungab
23827
772069
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
:
[[Mathc initiation/a522#Analyse IV : Les équations différentielles|Sommaire]]
:
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c19|* La méthode de Euler]]}}
:
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c20|* La méthode de Heun]]}}
:
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c21|* La méthode de Runge Kutta]]}}
:
{{AutoCat}}
7k5g4h4bdassuwqrr9rm26l15tcmrwg
Mathc initiation/a512
0
80964
771980
771974
2026-09-11T13:16:14Z
Xhungab
23827
771980
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
:
:
[[Mathc initiation/a522| Sommaire]]
:
.
:
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPP8YlloOLhIv5WflA0cpr5n&si=n_rhSX0G-o18nxFY Playlist]].'''
:
.
:
* La transformée de Laplace va nous permettre de passer du domaine temporel (t) au domaine fréquentiel (s).
* La transformée de Laplace va transformer les fonctions trigonométriques, exponentielles, ... en fonctions algébriques (+,-,*).
* La transformée de Laplace ne fonctionne que sur les '''fonctions causales'''.
[https://commons.wikimedia.org/wiki/File:Causal_function_with_gnuplot_and_C_language.png Une '''fonction causale'''] est une fonction qui ne prend sa valeur que quand t est supérieur à zéro. La fonction est nulle entre moins l'infini et zéro.
Ceci se matérialise sur l'intégrale dont les bornes sont entre zéro et l'infini positif.
L'intégrale de la transformée de Laplace de F(t) :
/ +oo
|
L{F(t)} = | exp(-s t) F(t) dt = f(s)
|
/ 0
* Si G(t) est une fonction (sin, cos, exp, t^n, ...) alors :
F(t) = G(t) * U(t) est une fonction causale, ou '''U(t)''' est la fonction [[Mathc initiation/a579| '''Heaviside''']].
U(t) = 0 si t < 0
U(t) = 1 si t >= 0
Pour simplifier la lecture de ce texte j'ai remplacé systématiquement '''G(t) * U(t)''' par '''F(t)'''.
* La transformée de Laplace est un '''opérateur linéaire''' :
L{a F(t) + b G(t)} = a L{F(t)} + b L{G(t)}
* La transformée inverse de Laplace est un '''opérateur linéaire''' :
L<sup>-1</sup>{a f(s) + b g(s)} = a L<sup>-1</sup>{f(s)} + b L<sup>-1</sup>{g(s)}
:
.
:
'''Se familiariser avec la transformée de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a513|* la Transformée de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a520|* la Transformée de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a536|* la Transformée de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a528|* La transformée de Laplace de la dérivée]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a532|* La transformée de Laplace de la dérivée seconde]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a524|* La transformée de Laplace d'une intégrale]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a559|* La transformée de Laplace : Multiplication par t^n]]}}
{{Partie{{{type|}}}|[[Mathc_initiation/a563|* La transformée de Laplace : Division par t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a540|* la Transformée de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a551|* la Transformée de Laplace : Translation de la variable t]]}}
:
.
:
'''Se familiariser avec la transformée Inverse de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a541|* la Transformée '''Inverse''' de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a542|* la Transformée '''Inverse''' de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a543|* la Transformée '''Inverse''' de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a546|* La transformée '''Inverse''' de Laplace de : (s)f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a545|* La transformée '''Inverse''' de Laplace de : (1/s)f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a554|* La transformée '''Inverse''' de Laplace de la dérivée de f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a555|* La transformée '''Inverse''' de Laplace de l'intégrale de f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a544|* la Transformée '''Inverse''' de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a552|* la Transformée '''Inverse''' de Laplace : Translation de la variable t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a570|* la Transformée '''Inverse''' de Laplace : La Méthode d'Expansion d'Heaviside]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a574|* la Transformée Inverse de Laplace : Le Théorème De Convolution]]}}
:
.
:
'''Quelques exercices : '''
{{Partie{{{type|}}}|[[Mathc initiation/a538|* Calculons : L{sin(t)sin(2t)} L{cos(3t)cos(2t)} L{sin(4t)cos(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a549|* Calculons : L{sinh(t)sinh(2t)} L{cosh(3t)cosh(2t)} L{sinh(4t)cosh(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a526|* Calculons : L{cos(2t)^2} L{sin(3t)^2} L{cos(2t)^3} L{sin(4t)^3}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c44a4|* Calculons : L{cosh(2t)^2} L{sinh(3t)^2} L{cosh(2t)^3} L{sinh(4t)^3}]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006o|* Calculons : L{exp(at)sin(bt)} L{exp(at)sinh(bt)}]]}}
:
.
:
'''Quelques applications : '''
{{Partie{{{type|}}}|[[Mathc initiation/a547|* Résoudre : y(t)'+y(t)=1 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a00ad|* Résoudre : y(t)'+3 y(t)=6 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006f|* Résoudre : y(t)'+2 y(t)=4 avec y(0) = 1]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006l|* Résoudre : y(t)'+2y(t)=exp(-t) avec y(0) = 1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006j|* Résoudre : y(t)'+3y(t)=exp(-2t) avec y(0) = 2]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a608|* Résoudre : y(t) ' ' - 9 y(t) = 0 avec y(0) = 1 et y(0)' = 3]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006h|* Résoudre : y(t) ' ' + 3 y(t)' + 2 y(t) = 0 avec y(0)=1 y(0)'=0]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a613|* Résoudre un sytème d'équations différentielles]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a580|* L'intégrale impropre en posant s tend vers 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a564|* L'intégrale impropre avec la multiplication par t^n avec n=1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a565|* L'intégrale impropre avec la division par t]]}}
:
.
:
'''Quelques rappels mathématiques : '''
{{Partie{{{type|}}}|[[Mathc initiation/a566|* Trigonométriques. Trigonométriques hyperboliques]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a569|* Fractions partielles]]}}
:
.
:
{{AutoCat}}
qvs6r64xct15e6ry60hukgxl198afi1
772046
771980
2026-09-11T22:08:59Z
Xhungab
23827
772046
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
:
:
[[Mathc initiation/a522| Sommaire]]
:
.
:
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPP8YlloOLhIv5WflA0cpr5n&si=n_rhSX0G-o18nxFY Playlist]].'''
:
.
:
* La transformée de Laplace va nous permettre de passer du domaine temporel (t) au domaine fréquentiel (s).
* La transformée de Laplace va transformer les fonctions trigonométriques, exponentielles, ... en fonctions algébriques (+,-,*).
* La transformée de Laplace ne fonctionne que sur les '''fonctions causales'''.
[https://commons.wikimedia.org/wiki/File:Causal_function_with_gnuplot_and_C_language.png Une '''fonction causale'''] est une fonction qui ne prend sa valeur que quand t est supérieur à zéro. La fonction est nulle entre moins l'infini et zéro.
Ceci se matérialise sur l'intégrale dont les bornes sont entre zéro et l'infini positif.
L'intégrale de la transformée de Laplace de F(t) :
/ +oo
|
L{F(t)} = | exp(-s t) F(t) dt = f(s)
|
/ 0
* Si G(t) est une fonction (sin, cos, exp, t^n, ...) alors :
F(t) = G(t) * U(t) est une fonction causale, ou '''U(t)''' est la fonction [[Mathc initiation/a579| '''Heaviside''']].
U(t) = 0 si t < 0
U(t) = 1 si t >= 0
Pour simplifier la lecture de ce texte j'ai remplacé systématiquement '''G(t) * U(t)''' par '''F(t)'''.
* La transformée de Laplace est un '''opérateur linéaire''' :
L{a F(t) + b G(t)} = a L{F(t)} + b L{G(t)}
* La transformée inverse de Laplace est un '''opérateur linéaire''' :
L<sup>-1</sup>{a f(s) + b g(s)} = a L<sup>-1</sup>{f(s)} + b L<sup>-1</sup>{g(s)}
:
.
:
'''Se familiariser avec la transformée de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a513|* la Transformée de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a520|* la Transformée de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a536|* la Transformée de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a528|* La transformée de Laplace de la dérivée]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a532|* La transformée de Laplace de la dérivée seconde]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a524|* La transformée de Laplace d'une intégrale]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a559|* La transformée de Laplace : Multiplication par t^n]]}}
{{Partie{{{type|}}}|[[Mathc_initiation/a563|* La transformée de Laplace : Division par t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a540|* la Transformée de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a551|* la Transformée de Laplace : Translation de la variable t]]}}
:
.
:
'''Se familiariser avec la transformée Inverse de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a541|* la Transformée '''Inverse''' de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a542|* la Transformée '''Inverse''' de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a543|* la Transformée '''Inverse''' de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a546|* La transformée '''Inverse''' de Laplace de : (s)f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a545|* La transformée '''Inverse''' de Laplace de : (1/s)f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a554|* La transformée '''Inverse''' de Laplace de la dérivée de f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a555|* La transformée '''Inverse''' de Laplace de l'intégrale de f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a544|* la Transformée '''Inverse''' de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a552|* la Transformée '''Inverse''' de Laplace : Translation de la variable t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a570|* la Transformée '''Inverse''' de Laplace : La Méthode d'Expansion d'Heaviside]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a574|* la Transformée Inverse de Laplace : Le Théorème De Convolution]]}}
:
.
:
'''Quelques exercices : '''
{{Partie{{{type|}}}|[[Mathc initiation/a538|* Calculons : L{sin(t)sin(2t)} L{cos(3t)cos(2t)} L{sin(4t)cos(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a549|* Calculons : L{sinh(t)sinh(2t)} L{cosh(3t)cosh(2t)} L{sinh(4t)cosh(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a526|* Calculons : L{cos(2t)^2} L{sin(3t)^2} L{cos(2t)^3} L{sin(4t)^3}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c44a4|* Calculons : L{cosh(2t)^2} L{sinh(3t)^2} L{cosh(2t)^3} L{sinh(4t)^3}]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006o|* Calculons : L{exp(at)sin(bt)} L{exp(at)sinh(bt)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006p|* Calculons : L{t sin(at)} L{t sinh(at)}]]}}
:
.
:
'''Quelques applications : '''
{{Partie{{{type|}}}|[[Mathc initiation/a547|* Résoudre : y(t)'+y(t)=1 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a00ad|* Résoudre : y(t)'+3 y(t)=6 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006f|* Résoudre : y(t)'+2 y(t)=4 avec y(0) = 1]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006l|* Résoudre : y(t)'+2y(t)=exp(-t) avec y(0) = 1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006j|* Résoudre : y(t)'+3y(t)=exp(-2t) avec y(0) = 2]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a608|* Résoudre : y(t) ' ' - 9 y(t) = 0 avec y(0) = 1 et y(0)' = 3]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006h|* Résoudre : y(t) ' ' + 3 y(t)' + 2 y(t) = 0 avec y(0)=1 y(0)'=0]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a613|* Résoudre un sytème d'équations différentielles]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a580|* L'intégrale impropre en posant s tend vers 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a564|* L'intégrale impropre avec la multiplication par t^n avec n=1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a565|* L'intégrale impropre avec la division par t]]}}
:
.
:
'''Quelques rappels mathématiques : '''
{{Partie{{{type|}}}|[[Mathc initiation/a566|* Trigonométriques. Trigonométriques hyperboliques]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a569|* Fractions partielles]]}}
:
.
:
{{AutoCat}}
hppiexa20vic6s83q1tmntmiw0fybm5
772065
772046
2026-09-12T09:12:28Z
Xhungab
23827
772065
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
:
:
[[Mathc initiation/Sommaire#Analyse IV : Se familiariser avec la Transformée de Laplace|Sommaire]]
:
.
:
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPP8YlloOLhIv5WflA0cpr5n&si=n_rhSX0G-o18nxFY Playlist]].'''
:
.
:
* La transformée de Laplace va nous permettre de passer du domaine temporel (t) au domaine fréquentiel (s).
* La transformée de Laplace va transformer les fonctions trigonométriques, exponentielles, ... en fonctions algébriques (+,-,*).
* La transformée de Laplace ne fonctionne que sur les '''fonctions causales'''.
[https://commons.wikimedia.org/wiki/File:Causal_function_with_gnuplot_and_C_language.png Une '''fonction causale'''] est une fonction qui ne prend sa valeur que quand t est supérieur à zéro. La fonction est nulle entre moins l'infini et zéro.
Ceci se matérialise sur l'intégrale dont les bornes sont entre zéro et l'infini positif.
L'intégrale de la transformée de Laplace de F(t) :
/ +oo
|
L{F(t)} = | exp(-s t) F(t) dt = f(s)
|
/ 0
* Si G(t) est une fonction (sin, cos, exp, t^n, ...) alors :
F(t) = G(t) * U(t) est une fonction causale, ou '''U(t)''' est la fonction [[Mathc initiation/a579| '''Heaviside''']].
U(t) = 0 si t < 0
U(t) = 1 si t >= 0
Pour simplifier la lecture de ce texte j'ai remplacé systématiquement '''G(t) * U(t)''' par '''F(t)'''.
* La transformée de Laplace est un '''opérateur linéaire''' :
L{a F(t) + b G(t)} = a L{F(t)} + b L{G(t)}
* La transformée inverse de Laplace est un '''opérateur linéaire''' :
L<sup>-1</sup>{a f(s) + b g(s)} = a L<sup>-1</sup>{f(s)} + b L<sup>-1</sup>{g(s)}
:
.
:
'''Se familiariser avec la transformée de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a513|* la Transformée de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a520|* la Transformée de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a536|* la Transformée de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a528|* La transformée de Laplace de la dérivée]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a532|* La transformée de Laplace de la dérivée seconde]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a524|* La transformée de Laplace d'une intégrale]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a559|* La transformée de Laplace : Multiplication par t^n]]}}
{{Partie{{{type|}}}|[[Mathc_initiation/a563|* La transformée de Laplace : Division par t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a540|* la Transformée de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a551|* la Transformée de Laplace : Translation de la variable t]]}}
:
.
:
'''Se familiariser avec la transformée Inverse de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a541|* la Transformée '''Inverse''' de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a542|* la Transformée '''Inverse''' de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a543|* la Transformée '''Inverse''' de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a546|* La transformée '''Inverse''' de Laplace de : (s)f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a545|* La transformée '''Inverse''' de Laplace de : (1/s)f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a554|* La transformée '''Inverse''' de Laplace de la dérivée de f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a555|* La transformée '''Inverse''' de Laplace de l'intégrale de f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a544|* la Transformée '''Inverse''' de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a552|* la Transformée '''Inverse''' de Laplace : Translation de la variable t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a570|* la Transformée '''Inverse''' de Laplace : La Méthode d'Expansion d'Heaviside]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a574|* la Transformée Inverse de Laplace : Le Théorème De Convolution]]}}
:
.
:
'''Quelques exercices : '''
{{Partie{{{type|}}}|[[Mathc initiation/a538|* Calculons : L{sin(t)sin(2t)} L{cos(3t)cos(2t)} L{sin(4t)cos(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a549|* Calculons : L{sinh(t)sinh(2t)} L{cosh(3t)cosh(2t)} L{sinh(4t)cosh(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a526|* Calculons : L{cos(2t)^2} L{sin(3t)^2} L{cos(2t)^3} L{sin(4t)^3}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c44a4|* Calculons : L{cosh(2t)^2} L{sinh(3t)^2} L{cosh(2t)^3} L{sinh(4t)^3}]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006o|* Calculons : L{exp(at)sin(bt)} L{exp(at)sinh(bt)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006p|* Calculons : L{t sin(at)} L{t sinh(at)}]]}}
:
.
:
'''Quelques applications : '''
{{Partie{{{type|}}}|[[Mathc initiation/a547|* Résoudre : y(t)'+y(t)=1 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a00ad|* Résoudre : y(t)'+3 y(t)=6 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006f|* Résoudre : y(t)'+2 y(t)=4 avec y(0) = 1]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006l|* Résoudre : y(t)'+2y(t)=exp(-t) avec y(0) = 1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006j|* Résoudre : y(t)'+3y(t)=exp(-2t) avec y(0) = 2]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a608|* Résoudre : y(t) ' ' - 9 y(t) = 0 avec y(0) = 1 et y(0)' = 3]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006h|* Résoudre : y(t) ' ' + 3 y(t)' + 2 y(t) = 0 avec y(0)=1 y(0)'=0]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a613|* Résoudre un sytème d'équations différentielles]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a580|* L'intégrale impropre en posant s tend vers 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a564|* L'intégrale impropre avec la multiplication par t^n avec n=1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a565|* L'intégrale impropre avec la division par t]]}}
:
.
:
'''Quelques rappels mathématiques : '''
{{Partie{{{type|}}}|[[Mathc initiation/a566|* Trigonométriques. Trigonométriques hyperboliques]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a569|* Fractions partielles]]}}
:
.
:
{{AutoCat}}
n8cdkttlzhpyjuaroti4v2zc39hnaju
772066
772065
2026-09-12T09:15:41Z
Xhungab
23827
772066
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
:
:
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la Transformée de Laplace|Sommaire]]
:
.
:
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPP8YlloOLhIv5WflA0cpr5n&si=n_rhSX0G-o18nxFY Playlist]].'''
:
.
:
* La transformée de Laplace va nous permettre de passer du domaine temporel (t) au domaine fréquentiel (s).
* La transformée de Laplace va transformer les fonctions trigonométriques, exponentielles, ... en fonctions algébriques (+,-,*).
* La transformée de Laplace ne fonctionne que sur les '''fonctions causales'''.
[https://commons.wikimedia.org/wiki/File:Causal_function_with_gnuplot_and_C_language.png Une '''fonction causale'''] est une fonction qui ne prend sa valeur que quand t est supérieur à zéro. La fonction est nulle entre moins l'infini et zéro.
Ceci se matérialise sur l'intégrale dont les bornes sont entre zéro et l'infini positif.
L'intégrale de la transformée de Laplace de F(t) :
/ +oo
|
L{F(t)} = | exp(-s t) F(t) dt = f(s)
|
/ 0
* Si G(t) est une fonction (sin, cos, exp, t^n, ...) alors :
F(t) = G(t) * U(t) est une fonction causale, ou '''U(t)''' est la fonction [[Mathc initiation/a579| '''Heaviside''']].
U(t) = 0 si t < 0
U(t) = 1 si t >= 0
Pour simplifier la lecture de ce texte j'ai remplacé systématiquement '''G(t) * U(t)''' par '''F(t)'''.
* La transformée de Laplace est un '''opérateur linéaire''' :
L{a F(t) + b G(t)} = a L{F(t)} + b L{G(t)}
* La transformée inverse de Laplace est un '''opérateur linéaire''' :
L<sup>-1</sup>{a f(s) + b g(s)} = a L<sup>-1</sup>{f(s)} + b L<sup>-1</sup>{g(s)}
:
.
:
'''Se familiariser avec la transformée de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a513|* la Transformée de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a520|* la Transformée de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a536|* la Transformée de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a528|* La transformée de Laplace de la dérivée]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a532|* La transformée de Laplace de la dérivée seconde]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a524|* La transformée de Laplace d'une intégrale]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a559|* La transformée de Laplace : Multiplication par t^n]]}}
{{Partie{{{type|}}}|[[Mathc_initiation/a563|* La transformée de Laplace : Division par t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a540|* la Transformée de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a551|* la Transformée de Laplace : Translation de la variable t]]}}
:
.
:
'''Se familiariser avec la transformée Inverse de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a541|* la Transformée '''Inverse''' de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a542|* la Transformée '''Inverse''' de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a543|* la Transformée '''Inverse''' de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a546|* La transformée '''Inverse''' de Laplace de : (s)f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a545|* La transformée '''Inverse''' de Laplace de : (1/s)f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a554|* La transformée '''Inverse''' de Laplace de la dérivée de f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a555|* La transformée '''Inverse''' de Laplace de l'intégrale de f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a544|* la Transformée '''Inverse''' de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a552|* la Transformée '''Inverse''' de Laplace : Translation de la variable t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a570|* la Transformée '''Inverse''' de Laplace : La Méthode d'Expansion d'Heaviside]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a574|* la Transformée Inverse de Laplace : Le Théorème De Convolution]]}}
:
.
:
'''Quelques exercices : '''
{{Partie{{{type|}}}|[[Mathc initiation/a538|* Calculons : L{sin(t)sin(2t)} L{cos(3t)cos(2t)} L{sin(4t)cos(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a549|* Calculons : L{sinh(t)sinh(2t)} L{cosh(3t)cosh(2t)} L{sinh(4t)cosh(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a526|* Calculons : L{cos(2t)^2} L{sin(3t)^2} L{cos(2t)^3} L{sin(4t)^3}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c44a4|* Calculons : L{cosh(2t)^2} L{sinh(3t)^2} L{cosh(2t)^3} L{sinh(4t)^3}]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006o|* Calculons : L{exp(at)sin(bt)} L{exp(at)sinh(bt)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006p|* Calculons : L{t sin(at)} L{t sinh(at)}]]}}
:
.
:
'''Quelques applications : '''
{{Partie{{{type|}}}|[[Mathc initiation/a547|* Résoudre : y(t)'+y(t)=1 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a00ad|* Résoudre : y(t)'+3 y(t)=6 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006f|* Résoudre : y(t)'+2 y(t)=4 avec y(0) = 1]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006l|* Résoudre : y(t)'+2y(t)=exp(-t) avec y(0) = 1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006j|* Résoudre : y(t)'+3y(t)=exp(-2t) avec y(0) = 2]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a608|* Résoudre : y(t) ' ' - 9 y(t) = 0 avec y(0) = 1 et y(0)' = 3]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006h|* Résoudre : y(t) ' ' + 3 y(t)' + 2 y(t) = 0 avec y(0)=1 y(0)'=0]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a613|* Résoudre un sytème d'équations différentielles]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a580|* L'intégrale impropre en posant s tend vers 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a564|* L'intégrale impropre avec la multiplication par t^n avec n=1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a565|* L'intégrale impropre avec la division par t]]}}
:
.
:
'''Quelques rappels mathématiques : '''
{{Partie{{{type|}}}|[[Mathc initiation/a566|* Trigonométriques. Trigonométriques hyperboliques]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a569|* Fractions partielles]]}}
:
.
:
{{AutoCat}}
jezyilndtdnpii0dp3kki0ujzt54s76
772071
772066
2026-09-12T09:20:04Z
Xhungab
23827
772071
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Les équations différentielles|Sommaire]]
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPP8YlloOLhIv5WflA0cpr5n&si=n_rhSX0G-o18nxFY Playlist]].'''
:
.
:
* La transformée de Laplace va nous permettre de passer du domaine temporel (t) au domaine fréquentiel (s).
* La transformée de Laplace va transformer les fonctions trigonométriques, exponentielles, ... en fonctions algébriques (+,-,*).
* La transformée de Laplace ne fonctionne que sur les '''fonctions causales'''.
[https://commons.wikimedia.org/wiki/File:Causal_function_with_gnuplot_and_C_language.png Une '''fonction causale'''] est une fonction qui ne prend sa valeur que quand t est supérieur à zéro. La fonction est nulle entre moins l'infini et zéro.
Ceci se matérialise sur l'intégrale dont les bornes sont entre zéro et l'infini positif.
L'intégrale de la transformée de Laplace de F(t) :
/ +oo
|
L{F(t)} = | exp(-s t) F(t) dt = f(s)
|
/ 0
* Si G(t) est une fonction (sin, cos, exp, t^n, ...) alors :
F(t) = G(t) * U(t) est une fonction causale, ou '''U(t)''' est la fonction [[Mathc initiation/a579| '''Heaviside''']].
U(t) = 0 si t < 0
U(t) = 1 si t >= 0
Pour simplifier la lecture de ce texte j'ai remplacé systématiquement '''G(t) * U(t)''' par '''F(t)'''.
* La transformée de Laplace est un '''opérateur linéaire''' :
L{a F(t) + b G(t)} = a L{F(t)} + b L{G(t)}
* La transformée inverse de Laplace est un '''opérateur linéaire''' :
L<sup>-1</sup>{a f(s) + b g(s)} = a L<sup>-1</sup>{f(s)} + b L<sup>-1</sup>{g(s)}
:
.
:
'''Se familiariser avec la transformée de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a513|* la Transformée de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a520|* la Transformée de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a536|* la Transformée de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a528|* La transformée de Laplace de la dérivée]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a532|* La transformée de Laplace de la dérivée seconde]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a524|* La transformée de Laplace d'une intégrale]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a559|* La transformée de Laplace : Multiplication par t^n]]}}
{{Partie{{{type|}}}|[[Mathc_initiation/a563|* La transformée de Laplace : Division par t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a540|* la Transformée de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a551|* la Transformée de Laplace : Translation de la variable t]]}}
:
.
:
'''Se familiariser avec la transformée Inverse de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a541|* la Transformée '''Inverse''' de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a542|* la Transformée '''Inverse''' de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a543|* la Transformée '''Inverse''' de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a546|* La transformée '''Inverse''' de Laplace de : (s)f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a545|* La transformée '''Inverse''' de Laplace de : (1/s)f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a554|* La transformée '''Inverse''' de Laplace de la dérivée de f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a555|* La transformée '''Inverse''' de Laplace de l'intégrale de f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a544|* la Transformée '''Inverse''' de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a552|* la Transformée '''Inverse''' de Laplace : Translation de la variable t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a570|* la Transformée '''Inverse''' de Laplace : La Méthode d'Expansion d'Heaviside]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a574|* la Transformée Inverse de Laplace : Le Théorème De Convolution]]}}
:
.
:
'''Quelques exercices : '''
{{Partie{{{type|}}}|[[Mathc initiation/a538|* Calculons : L{sin(t)sin(2t)} L{cos(3t)cos(2t)} L{sin(4t)cos(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a549|* Calculons : L{sinh(t)sinh(2t)} L{cosh(3t)cosh(2t)} L{sinh(4t)cosh(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a526|* Calculons : L{cos(2t)^2} L{sin(3t)^2} L{cos(2t)^3} L{sin(4t)^3}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c44a4|* Calculons : L{cosh(2t)^2} L{sinh(3t)^2} L{cosh(2t)^3} L{sinh(4t)^3}]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006o|* Calculons : L{exp(at)sin(bt)} L{exp(at)sinh(bt)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006p|* Calculons : L{t sin(at)} L{t sinh(at)}]]}}
:
.
:
'''Quelques applications : '''
{{Partie{{{type|}}}|[[Mathc initiation/a547|* Résoudre : y(t)'+y(t)=1 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a00ad|* Résoudre : y(t)'+3 y(t)=6 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006f|* Résoudre : y(t)'+2 y(t)=4 avec y(0) = 1]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006l|* Résoudre : y(t)'+2y(t)=exp(-t) avec y(0) = 1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006j|* Résoudre : y(t)'+3y(t)=exp(-2t) avec y(0) = 2]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a608|* Résoudre : y(t) ' ' - 9 y(t) = 0 avec y(0) = 1 et y(0)' = 3]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006h|* Résoudre : y(t) ' ' + 3 y(t)' + 2 y(t) = 0 avec y(0)=1 y(0)'=0]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a613|* Résoudre un sytème d'équations différentielles]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a580|* L'intégrale impropre en posant s tend vers 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a564|* L'intégrale impropre avec la multiplication par t^n avec n=1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a565|* L'intégrale impropre avec la division par t]]}}
:
.
:
'''Quelques rappels mathématiques : '''
{{Partie{{{type|}}}|[[Mathc initiation/a566|* Trigonométriques. Trigonométriques hyperboliques]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a569|* Fractions partielles]]}}
:
.
:
{{AutoCat}}
5vnikrxirunigxcrupkk7ydu1i86j60
772073
772071
2026-09-12T09:21:27Z
Xhungab
23827
772073
wikitext
text/x-wiki
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Les équations différentielles|Sommaire]]
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPP8YlloOLhIv5WflA0cpr5n&si=n_rhSX0G-o18nxFY Playlist]].'''
:
.
:
* La transformée de Laplace va nous permettre de passer du domaine temporel (t) au domaine fréquentiel (s).
* La transformée de Laplace va transformer les fonctions trigonométriques, exponentielles, ... en fonctions algébriques (+,-,*).
* La transformée de Laplace ne fonctionne que sur les '''fonctions causales'''.
[https://commons.wikimedia.org/wiki/File:Causal_function_with_gnuplot_and_C_language.png Une '''fonction causale'''] est une fonction qui ne prend sa valeur que quand t est supérieur à zéro. La fonction est nulle entre moins l'infini et zéro.
Ceci se matérialise sur l'intégrale dont les bornes sont entre zéro et l'infini positif.
L'intégrale de la transformée de Laplace de F(t) :
/ +oo
|
L{F(t)} = | exp(-s t) F(t) dt = f(s)
|
/ 0
* Si G(t) est une fonction (sin, cos, exp, t^n, ...) alors :
F(t) = G(t) * U(t) est une fonction causale, ou '''U(t)''' est la fonction [[Mathc initiation/a579| '''Heaviside''']].
U(t) = 0 si t < 0
U(t) = 1 si t >= 0
Pour simplifier la lecture de ce texte j'ai remplacé systématiquement '''G(t) * U(t)''' par '''F(t)'''.
* La transformée de Laplace est un '''opérateur linéaire''' :
L{a F(t) + b G(t)} = a L{F(t)} + b L{G(t)}
* La transformée inverse de Laplace est un '''opérateur linéaire''' :
L<sup>-1</sup>{a f(s) + b g(s)} = a L<sup>-1</sup>{f(s)} + b L<sup>-1</sup>{g(s)}
:
.
:
'''Se familiariser avec la transformée de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a513|* la Transformée de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a520|* la Transformée de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a536|* la Transformée de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a528|* La transformée de Laplace de la dérivée]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a532|* La transformée de Laplace de la dérivée seconde]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a524|* La transformée de Laplace d'une intégrale]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a559|* La transformée de Laplace : Multiplication par t^n]]}}
{{Partie{{{type|}}}|[[Mathc_initiation/a563|* La transformée de Laplace : Division par t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a540|* la Transformée de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a551|* la Transformée de Laplace : Translation de la variable t]]}}
:
.
:
'''Se familiariser avec la transformée Inverse de Laplace : '''
{{Partie{{{type|}}}|[[Mathc initiation/a541|* la Transformée '''Inverse''' de Laplace : Première approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a542|* la Transformée '''Inverse''' de Laplace : Deuxième approche]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a543|* la Transformée '''Inverse''' de Laplace : Changement d'échelle]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a546|* La transformée '''Inverse''' de Laplace de : (s)f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a545|* La transformée '''Inverse''' de Laplace de : (1/s)f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a554|* La transformée '''Inverse''' de Laplace de la dérivée de f(s)]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a555|* La transformée '''Inverse''' de Laplace de l'intégrale de f(s)]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a544|* la Transformée '''Inverse''' de Laplace : Translation de la variable s]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a552|* la Transformée '''Inverse''' de Laplace : Translation de la variable t]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a570|* la Transformée '''Inverse''' de Laplace : La Méthode d'Expansion d'Heaviside]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a574|* la Transformée Inverse de Laplace : Le Théorème De Convolution]]}}
:
.
:
'''Quelques exercices : '''
{{Partie{{{type|}}}|[[Mathc initiation/a538|* Calculons : L{sin(t)sin(2t)} L{cos(3t)cos(2t)} L{sin(4t)cos(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a549|* Calculons : L{sinh(t)sinh(2t)} L{cosh(3t)cosh(2t)} L{sinh(4t)cosh(3t)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a526|* Calculons : L{cos(2t)^2} L{sin(3t)^2} L{cos(2t)^3} L{sin(4t)^3}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/Fichiers h : c44a4|* Calculons : L{cosh(2t)^2} L{sinh(3t)^2} L{cosh(2t)^3} L{sinh(4t)^3}]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006o|* Calculons : L{exp(at)sin(bt)} L{exp(at)sinh(bt)}]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006p|* Calculons : L{t sin(at)} L{t sinh(at)}]]}}
:
.
:
'''Quelques applications : '''
{{Partie{{{type|}}}|[[Mathc initiation/a547|* Résoudre : y(t)'+y(t)=1 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a00ad|* Résoudre : y(t)'+3 y(t)=6 avec y(0) = 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006f|* Résoudre : y(t)'+2 y(t)=4 avec y(0) = 1]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/006l|* Résoudre : y(t)'+2y(t)=exp(-t) avec y(0) = 1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006j|* Résoudre : y(t)'+3y(t)=exp(-2t) avec y(0) = 2]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a608|* Résoudre : y(t) ' ' - 9 y(t) = 0 avec y(0) = 1 et y(0)' = 3]]}}
{{Partie{{{type|}}}|[[Mathc initiation/006h|* Résoudre : y(t) ' ' + 3 y(t)' + 2 y(t) = 0 avec y(0)=1 y(0)'=0]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a613|* Résoudre un sytème d'équations différentielles]]}}
:
.
:
{{Partie{{{type|}}}|[[Mathc initiation/a580|* L'intégrale impropre en posant s tend vers 0]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a564|* L'intégrale impropre avec la multiplication par t^n avec n=1]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a565|* L'intégrale impropre avec la division par t]]}}
:
.
:
'''Quelques rappels mathématiques : '''
{{Partie{{{type|}}}|[[Mathc initiation/a566|* Trigonométriques. Trigonométriques hyperboliques]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a569|* Fractions partielles]]}}
:
.
:
{{AutoCat}}
j5ggslzqninjp3qdaxuhxxtie1b4vwc
Mathc initiation/a584
0
81062
772068
771924
2026-09-12T09:17:09Z
Xhungab
23827
772068
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
:
:
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée en Z|Sommaire]]
:
{{Partie{{{type|}}}|Se familiariser avec la transformée en Z :}}
Soit f(n) un signal causal discret :
_oo+__
\
La transformée en Z de f(n) est F(z) = | f(n) z^(-n)
/_____
n = 0
1 1 1 1
Soit = f(0) ----- + f(1) ----- + f(2) ----- + ... + f(n) -----
z^(0) z^(1) z^(2) z^(n)
{{Partie{{{type|}}}|[[Mathc initiation/a581|* Les suites usuelles :]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a582|* Transformées en Z usuelles, des signaux causales discrets ::]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a583|* Propriétés des transformées en Z : ]]}}
Se familiariser avec '''la transformée en Z Inverse''' :
Pour ce familiariser avec '''la transformée en Z''', je vous conseille de voir les sections précédentes en étudiant les colonnes de gauche à droite.
Pour '''la transformée en Z inverse''', il suffit de travailler avec les colonnes de droite à gauche.
Il faudra se familiariser avec [[Mathc initiation/a569|la méthode des fractions partielles]], et la division des polynômes. Vous pouvez revoir [[Mathc initiation/Fichiers h : c18|La méthode de Horner (Factoriser, diviser des polynômes)]]
Les méthodes plus avancées dépassent mes compétences.
:
{{AutoCat}}
nsyz1tr6kpe9h7dw1q0dtu25jqmy20y
772072
772068
2026-09-12T09:20:24Z
Xhungab
23827
772072
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Les équations différentielles|Sommaire]]
{{Partie{{{type|}}}|Se familiariser avec la transformée en Z :}}
Soit f(n) un signal causal discret :
_oo+__
\
La transformée en Z de f(n) est F(z) = | f(n) z^(-n)
/_____
n = 0
1 1 1 1
Soit = f(0) ----- + f(1) ----- + f(2) ----- + ... + f(n) -----
z^(0) z^(1) z^(2) z^(n)
{{Partie{{{type|}}}|[[Mathc initiation/a581|* Les suites usuelles :]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a582|* Transformées en Z usuelles, des signaux causales discrets ::]]}}
{{Partie{{{type|}}}|[[Mathc initiation/a583|* Propriétés des transformées en Z : ]]}}
Se familiariser avec '''la transformée en Z Inverse''' :
Pour ce familiariser avec '''la transformée en Z''', je vous conseille de voir les sections précédentes en étudiant les colonnes de gauche à droite.
Pour '''la transformée en Z inverse''', il suffit de travailler avec les colonnes de droite à gauche.
Il faudra se familiariser avec [[Mathc initiation/a569|la méthode des fractions partielles]], et la division des polynômes. Vous pouvez revoir [[Mathc initiation/Fichiers h : c18|La méthode de Horner (Factoriser, diviser des polynômes)]]
Les méthodes plus avancées dépassent mes compétences.
:
{{AutoCat}}
o03ulcm8l9o80hnjl73ecpkjj3l1q79
Mathc initiation/a592
0
81086
772067
771922
2026-09-12T09:16:30Z
Xhungab
23827
772067
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
:
:
[[Mathc initiation/a522#Analyse IV : Se familiariser avec la transformée de Fourier discrète|Sommaire]]
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPNHUnib_ehtvFhNxJMWqn5Y&si=KIbSRJw_xjHl815R Playlist]].'''
{{Partie{{{type|}}}| Se familiariser avec la transformée de Fourier discrète :}}
En mathématiques, la transformée de Fourier discrète (TFD, Eng. DFT) sert à traiter un signal numérique. Elle constitue un équivalent discret (c'est-à-dire pour un signal défini à partir d'un nombre fini d'échantillons) de la transformée de Fourier (continue) utilisée pour traiter un signal analogique. Plus précisément, la TFD est la représentation spectrale discrète dans le domaine des fréquences d'un signal échantillonné. [https://fr.wikipedia.org/wiki/Transformation_de_Fourier_discr%C3%A8te Wikipédia]
La transformée de Fourier discrète d'un signal s de N échantillons (s(0), s(1),..., s(N-1) est un vecteur (S(0),S(1), ..., S(N-1)) défini par :
_ N-1_
\
S(k) = | s(n) exp(-i 2kPI/N)^n pour 0 <= k < N
/_n=0_
On obtient ainsi une représentation spectrale discrète du signal échantillonné s(n).
Notre étude nous a permis de comprendre que l'on pouvait utiliser le calcul matriciel pour effectuer ce travail. Dans un premier temps nous construirons manuellement cette matrice. Dans un deuxième temps, nous demanderons au langage C de construire cette matrice.
Nous verrons qu'en prenant le conjugué, multiplié par l'inverse de N, (le nombre d'échantillons), nous allons obtenir la transformée de Fourier discrete inverse.
Pour travailler sur des matrices complexes, nous utiliserons le livre [[Mathc complexes/a277|Matrices pour la transformée de Fourier discrète]].
:
{{AutoCat}}
q4m0798hx0n997f9wg8x8jcsy3cen91
772070
772067
2026-09-12T09:19:26Z
Xhungab
23827
772070
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a522#Analyse IV : Les équations différentielles|Sommaire]]
'''L'étude de ce chapitre peut ce faire à l'aide de cette [[https://youtube.com/playlist?list=PLi6peGpf8EPNHUnib_ehtvFhNxJMWqn5Y&si=KIbSRJw_xjHl815R Playlist]].'''
{{Partie{{{type|}}}| Se familiariser avec la transformée de Fourier discrète :}}
En mathématiques, la transformée de Fourier discrète (TFD, Eng. DFT) sert à traiter un signal numérique. Elle constitue un équivalent discret (c'est-à-dire pour un signal défini à partir d'un nombre fini d'échantillons) de la transformée de Fourier (continue) utilisée pour traiter un signal analogique. Plus précisément, la TFD est la représentation spectrale discrète dans le domaine des fréquences d'un signal échantillonné. [https://fr.wikipedia.org/wiki/Transformation_de_Fourier_discr%C3%A8te Wikipédia]
La transformée de Fourier discrète d'un signal s de N échantillons (s(0), s(1),..., s(N-1) est un vecteur (S(0),S(1), ..., S(N-1)) défini par :
_ N-1_
\
S(k) = | s(n) exp(-i 2kPI/N)^n pour 0 <= k < N
/_n=0_
On obtient ainsi une représentation spectrale discrète du signal échantillonné s(n).
Notre étude nous a permis de comprendre que l'on pouvait utiliser le calcul matriciel pour effectuer ce travail. Dans un premier temps nous construirons manuellement cette matrice. Dans un deuxième temps, nous demanderons au langage C de construire cette matrice.
Nous verrons qu'en prenant le conjugué, multiplié par l'inverse de N, (le nombre d'échantillons), nous allons obtenir la transformée de Fourier discrete inverse.
Pour travailler sur des matrices complexes, nous utiliserons le livre [[Mathc complexes/a277|Matrices pour la transformée de Fourier discrète]].
:
{{AutoCat}}
0nq8urtaldiaflxnb440v4i0tfl20qi
Discussion utilisateur:Sicarov
3
81487
771984
723689
2026-09-11T14:56:37Z
Xhungab
23827
771984
wikitext
text/x-wiki
{| class="bienvenue-cadre flexible" align="center" style="width:100%;margin-bottom:0.5em; border:1px solid #8884;"
|-
! colspan="2" class="bienvenue-titre" style="padding:5px; border:1px solid #8888; background-color:#2323ff4a; font-size:130%;" |
Bienvenue sur Wikilivres, {{BASEPAGENAME}} !
|-
| colspan="2" align="center" style="font-size:110%;" |
Wikilivres est un projet francophone de rédactions de manuels pédagogiques libres.
|-
! [[Image:Crystal Clear app Login Manager.png|55px|lien=]]
| style="width:95%;" |
Si vous désirez vous investir dans ce projet passionnant, vous êtes le bienvenu.
Tous les contributeurs de Wikilivres vous invitent à corriger et développer les livres existants et à créer le vôtre !
Prenez avant toute chose connaissance des '''[[Wikilivres:Présentation|principes fondateurs de Wikilivres]]''' à la base du projet.
N'ayez aucune crainte d'abîmer la bibliothèque : toutes les modifications sont suivies par des contributeurs plus expérimentés qui pourront corriger vos éventuelles erreurs.
Ne vous offensez pas de ces interventions ni des messages destinés à vous aider à comprendre le projet et ses règles.
Suivez leurs conseils et n'hésitez pas à demander simplement plus d'explications !
|-
! [[Image:ACE.png|55px|lien=]]
|
Lorsque vous contribuez, '''gardez à l'esprit que Wikilivres est une ''bibliothèque universelle''''', il faut donc veiller à '''respecter un point de vue vérifiable'''.
De plus, le contenu doit rester conforme à notre licence d'utilisation libre (GFDL).
Les utilisateurs travaillent en harmonie et privilégient la discussion aussi courtoise que possible pour régler les oppositions, aussi, prenez le temps de vous y habituer.
Pensez à vous présenter également sur votre [[Spécial:MaPage|page d'utilisateur]], ce qui permettra à chacun de connaître vos centres d'intérêt et de mieux vous guider vers les divers projets thématiques.
Pour cela, vous pouvez employer les [[Wikilivres:Boîtes utilisateur|boîtes utilisateur]].<br />
Bien qu'il s'agisse d'un travail de rédaction complexe résultant de l'action de milliers de contributeurs francophones du monde entier, sa philosophie peut être résumée en quelques mots : '''« N'hésitez pas à l'améliorer ! »'''
|-
! [[Image:Plume pen w.png|55px|lien=]]
|
Au cours d'une discussion, n'oubliez pas de signer vos messages, à l'aide de quatre tildes (<nowiki>~~~~</nowiki>) ou du bouton [[Image:Signature icon april 2018 - 2.png|25px|lien=]] présent en haut de la fenêtre de modification.
En revanche ne signez pas les livres lorsque vous en créez ou que vous les modifiez car l'historique permet de retrouver toutes vos contributions.
|-
! [[Image:Wikibooks logo.svg|55px|lien=]]
|
Vous pouvez trouver des explications détaillées à partir des pages :
* '''''[[Aide:Accueil|Tout l'indispensable]]''''', à lire absolument ;
* '''[[Aide:Syntaxe|Syntaxe]]''', les conventions d'écriture à connaître ;
* '''[[Aide:Poser une question|Poser une question]]''', si vous vous sentez perdu ;
* '''[[Aide:Comment modifier une page|Comment modifier une page]]'''.
Vous pourrez ajouter par la suite d'autres pages d'aide ou les informations dont vous pensez avoir besoin dans votre [[Spécial:MaPage|espace utilisateur]].
Si vous souhaitez contribuer mais que vous n'avez pas d'idées, allez faire un tour sur [[Wikilivres:Demande de livre|les demandes de livres et de traductions]].
Bonne contribution, et n'hésitez pas à me contacter si besoin !
<div style="font-size:85%;">PS: If you don't speak French, you can consult the [[Wikilivres:Ambassade|embassy]] for help.</div>
|}
[[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 4 août 2024 à 11:49 (CEST)
== Demandes de prises de décision ==
Bonjour, Pour info.
J'ai posé deux demandes de prises de décisions concernant le sujet suivant.
* '''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Cr%C3%A9er_un_espace_lecteurs_et_un_espace_auteurs_sur_la_page_d%27accueil Créer un espace lecteurs et un espace Les coulisses de Wikibook]'''
* '''[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]'''
Merci [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 16:56 (CEST)
083v8fvxt09i194riy0sr6md536id2k
Fonctionnement d'un ordinateur/Les modes d'adressage
0
82238
771989
763684
2026-09-11T17:54:25Z
Mewtow
31375
/* L'utilité des pointeurs : les structures de données */
771989
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Le mode d''''adressage base + indice''' est utilisé lors de l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. L'adressage base + indice, fournit à la fois l'adresse de base du tableau et l'indice de l’élément voulu. Les deux sont dans un registre, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Il existe une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique. L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage inhérent. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Les deux modes d'adressage précédents sont appelés des '''modes d'adressage indicés''', car ils gèrent automatiquement l'indice. Ils existent en deux variantes, assez similaires. La première variante ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. Pour éviter ce genre de choses, la seconde variante se charge automatiquement de gérer la taille de la donnée. Le programme doit donc incrémenter/décrémenter les indices normalement, par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée. Cette dernière est généralement encodée dans l'instruction, qui gère des tailles de données basiques 1, 2, 4, 8 octets, guère plus.
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
il8pmqcx1axmvy5xcjykeko22mus682
771991
771989
2026-09-11T17:55:44Z
Mewtow
31375
/* L'utilité des pointeurs : les structures de données */
771991
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Le mode d''''adressage base + indice''' est utilisé lors de l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. L'adressage base + indice, fournit à la fois l'adresse de base du tableau et l'indice de l’élément voulu. Les deux sont dans un registre, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Il existe une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique. L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage inhérent. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Les deux modes d'adressage précédents sont appelés des '''modes d'adressage indicés''', car ils gèrent automatiquement l'indice. Ils existent en deux variantes, assez similaires. La première variante ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. Pour éviter ce genre de choses, la seconde variante se charge automatiquement de gérer la taille de la donnée. Le programme doit donc incrémenter/décrémenter les indices normalement, par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée. Cette dernière est généralement encodée dans l'instruction, qui gère des tailles de données basiques 1, 2, 4, 8 octets, guère plus.
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
hdcshbn2t0ywqgpn6th452w6gtctv87
771993
771991
2026-09-11T18:08:58Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
771993
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et cela permet de distinguer deux sous-types d'adressage base + indice.
La première variante ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4.
La seconde variante se charge automatiquement de gérer la taille de la donnée. Le programme doit donc incrémenter/décrémenter les indices normalement, par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée. Cette dernière est généralement encodée dans l'instruction, qui gère des tailles de données basiques 1, 2, 4, 8 octets, guère plus. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''scaled''''' index adressing'').
Il existe une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique. L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage inhérent. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
kdtakhe72sv064sth9rs9ct5gny8tad
771994
771993
2026-09-11T18:24:55Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
771994
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc multiplier l'indice par la taille de la donnée lui-même. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures. Pour parcourir le tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4.
La seconde solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La troisième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''scaled''''' index adressing'').
Une quatrième solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE.
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique. L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage inhérent. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
9oiad0zmky2nctymnltl1g3qngk241s
771995
771994
2026-09-11T18:32:35Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
771995
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc multiplier l'indice par la taille de la donnée lui-même. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures. Pour parcourir le tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4.
La seconde solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La troisième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''scaled''''' index adressing'').
Une quatrième solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique. L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage inhérent. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
qk2ou0mja4yji8tmntm23nupuobiqjj
771996
771995
2026-09-11T18:38:44Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
771996
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc multiplier l'indice par la taille de la donnée lui-même. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures. Pour parcourir le tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4.
La seconde solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La troisième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''scaled''''' index adressing'').
Une quatrième solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique. L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage inhérent. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
d0y8q7wo63o71o50ot3c2lpa5qv9vc9
771997
771996
2026-09-11T18:40:38Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
771997
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc multiplier l'indice par la taille de la donnée lui-même. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures. Pour parcourir le tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4.
La seconde solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La troisième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''scaled''''' index adressing'').
Une quatrième solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
17oocba56zx8reey3xfcydnh9ayo2ro
771998
771997
2026-09-11T18:41:19Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
771998
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme doit donc multiplier l'indice par la taille de la donnée lui-même. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures. Pour parcourir le tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4.
La seconde solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La troisième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Une quatrième solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
l5bjc4j8yy6xokoy75kn4fgoh3lm15g
771999
771998
2026-09-11T18:42:48Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
771999
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements : il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Une donnée a une place prédéterminée dans un enregistrement : elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément d'un enregistrement se calcule en ajoutant une constante à l'adresse de départ de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
iles0liz60bjoaewn4j7mefsgstipfn
772000
771999
2026-09-11T18:52:52Z
Mewtow
31375
/* Les modes d'adressage indirect à décalage pour les enregistrements */
772000
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement, ce qui fait qu'elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrement doivent avoir la même taille. Et ils sont de même type, à savoir que leur organisation interne est la même.
L'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élèment dans un enregistrement, il faut ajouter la position de cet élement dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Les processeurs x86 disposent d'un tel mode d'adressage.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
819vbeztca3fsrwdovbgmcdl40crgqy
772001
772000
2026-09-11T18:59:29Z
Mewtow
31375
/* Les modes d'adressage indirect à décalage pour les enregistrements */
772001
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement, ce qui fait qu'elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille. Et ils sont de même type, à savoir que leur organisation interne est la même.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
L'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élèment dans un enregistrement, il faut ajouter la position de cet élement dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage.
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est assez limité.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
bniazu5vahvrcuc6ughf2vq0g7c2qsl
772002
772001
2026-09-11T19:05:22Z
Mewtow
31375
/* Les modes d'adressage indirect à décalage pour les enregistrements */
772002
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement, ce qui fait qu'elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille. Et ils sont de même type, à savoir que leur organisation interne est la même.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
L'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élèment dans un enregistrement, il faut ajouter la position de cet élement dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Le mode d'adressage a besoin pour fonctionner de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est assez limité.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
0xezlv3t1puwaueoskc15kn51kvhk6j
772003
772002
2026-09-11T19:21:45Z
Mewtow
31375
/* Les modes d'adressage indirect à décalage pour les enregistrements */
772003
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement, ce qui fait qu'elle est donc a une distance fixe du début de celui-ci. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille. Et ils sont de même type, à savoir que leur organisation interne est la même.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
L'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élèment dans un enregistrement, il faut ajouter la position de cet élement dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Le mode d'adressage a besoin pour fonctionner de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
jd3wrskkph4y3halsjlj48pg3921qmm
772006
772003
2026-09-11T19:37:23Z
Mewtow
31375
/* Les modes d'adressage indirect à décalage pour les enregistrements */
772006
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Le mode d'adressage base + indice °+ décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille. Et ils sont de même type, à savoir que leur organisation interne est la même.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
L'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élèment dans un enregistrement, il faut ajouter la position de cet élement dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Le mode d'adressage a besoin pour fonctionner de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
0hx0vc21i8aakzxtjlxztx6nkx8f133
772007
772006
2026-09-11T19:37:43Z
Mewtow
31375
/* Le mode d'adressage base + indice °+ décalage pour les tableaux d'enregistrements */
772007
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille. Et ils sont de même type, à savoir que leur organisation interne est la même.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
L'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élèment dans un enregistrement, il faut ajouter la position de cet élement dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Il calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Et le tout, en un seul mode d'adressage. Le mode d'adressage a besoin pour fonctionner de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
4opewd2mqgt5zkagcyo1d7b99lu4z1i
772008
772007
2026-09-11T19:46:23Z
Mewtow
31375
/* Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements */
772008
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux. Les données de la structure sont placées les unes à la suite des autres dans la RAM, à partir d'une adresse de début. Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
grp7mecvcqhmwju7xvwgw3lhhvvl6a5
772010
772008
2026-09-11T19:52:01Z
Mewtow
31375
/* L'utilité des pointeurs : les structures de données */
772010
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressage indirects sont des variantes des modes d'adressages directs. Par exemple, le mode d'adressage inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur, qui pointe vers une donnée en RAM/ROM. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite reste de l'adressage implicite, peu importe qu'il adresse une donnée ou un pointeur. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Pour résumer, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
6u39tzuo7yhl0fde9ht990jn97zxzd3
772015
772010
2026-09-11T19:58:07Z
Mewtow
31375
/* L'adressage indirect à registre pour les pointeurs */
772015
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
===L'utilité des pointeurs : les structures de données===
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouve très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Les structures de données les plus simples sont appelées "structures" ou '''enregistrements'''. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Localiser une donnée dans la structure demande simplement de connaitre à combien de byte se situe la donnée par rapport à l'adresse de début. Une simple addition permet de calculer cette adresse, et des modes d'adressage permettent de faire ce calcul implicitement.
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gére pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
Pour faciliter ces parcours de tableaux, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Les modes d'adressage indirects indicés pour les tableaux===
Les deux modes d'adressage que nous allons voir sont utilisés pour l'accès à un tableau, quand on veut lire/écrire un élément de ce tableau. Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les tableaux, nous allons voir des modes d'adressage spécialisés dans les enregistrements, aussi appelées structures en langage C. Elles regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements. Il est obligé de manipuler les données élémentaires qui le constituent une par une. Pour cela, il doit calculer leur adresse, et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
L'adresse d'un élément demande de connaitre l'adresse de l'enregistrement et la position de la donnée dans l'enregistrement. Une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à l'adresse de l'enregistrement. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre et une constante. Le registre contient l'adresse du début de l'enregistrement, un pointeur vers l'enregistrement.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
9e8ha66hd0xyiad1v9ujs6282j0p9u1
772019
772015
2026-09-11T20:10:28Z
Mewtow
31375
/* Les modes d'adressage indirects pour les pointeurs */
772019
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage indirect à décalage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Les modes d'adressage indirects indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les deux modes d'adressage que nous allons voir font ce calcul d'adresse automatiquement, quand on veut lire/écrire un élément de ce tableau Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
tvjy1h1h5kyoukzj16lw92py0006i6g
772020
772019
2026-09-11T20:10:44Z
Mewtow
31375
/* Les modes d'adressage indirect à décalage pour les enregistrements */
772020
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Les modes d'adressage indirects indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les deux modes d'adressage que nous allons voir font ce calcul d'adresse automatiquement, quand on veut lire/écrire un élément de ce tableau Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
shswcc17m5twoeeceudsaz5wq1fl4il
772021
772020
2026-09-11T20:10:52Z
Mewtow
31375
/* Les modes d'adressage indirects indicés pour les tableaux */
772021
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les deux modes d'adressage que nous allons voir font ce calcul d'adresse automatiquement, quand on veut lire/écrire un élément de ce tableau Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
i0m8rkyodm5zevzfc2mbbn0m9zqb0pk
772023
772021
2026-09-11T20:18:44Z
Mewtow
31375
/* Les modes d'adressage indirects pour les pointeurs */
772023
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les deux modes d'adressage que nous allons voir font ce calcul d'adresse automatiquement, quand on veut lire/écrire un élément de ce tableau Et pour être plus précis, ils servent pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux de structures sera vu à part, nous allons nous concentrer sur les tableaux dont les éléments sont nativement supportés par le processeur, des éléments qui tiennent dans un registre.
Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. Les deux modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
5k1w7swa77wa6tngd80snk3sfqavy1x
772024
772023
2026-09-11T20:22:23Z
Mewtow
31375
/* Les modes d'adressage indicés pour les tableaux */
772024
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les deux modes d'adressage que nous allons voir font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, ils font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage qui vont suivre gèrent automatiquement l'indice, ce qui fait qu'ils sont appelés des '''modes d'adressage indicés'''. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
e0dggcjgk0sunqaz34hsvm2rmglqh9e
772026
772024
2026-09-11T20:23:34Z
Mewtow
31375
/* Les modes d'adressage indicés pour les tableaux */
772026
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Ils portent ce nom car ils gèrent automatiquement l'indice. L'indice est précisé dans un registre, disons un registre général. La différence entre les deux se trouve au niveau de l'adresse de base.
Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Avec le mode d''''adressage base + indice''', l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
c16tpo0pon9j7n9hl24b5qaex6lt1yl
772027
772026
2026-09-11T20:29:11Z
Mewtow
31375
/* Les modes d'adressage indicés pour les tableaux */
772027
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est généralement mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, la technique n'est plus utilisée de nos jours.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
sj8v9jrml5pa0tknih5ekae5spl7m4u
772028
772027
2026-09-11T20:31:13Z
Mewtow
31375
/* Les modes d'adressage indicés pour les tableaux */
772028
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs''', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
lqwvlxt8jg4d4asg9uax5qdb0k2o8ym
772029
772028
2026-09-11T20:34:50Z
Mewtow
31375
/* Les modes d'adressage indicés pour les tableaux */
772029
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage indirects pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage précédent, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
ey60m0s29g1j71xhk7eu7hsr7t805m3
772030
772029
2026-09-11T20:35:28Z
Mewtow
31375
/* Les modes d'adressage indirects pour le parcours d'un tableau */
772030
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
g52ddirqmp8gceycpgxn70mxsxwtj6q
772031
772030
2026-09-11T20:39:52Z
Mewtow
31375
/* Les modes d'adressage pour le parcours d'un tableau */
772031
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Il faut encore faire des calculs d'adresse pour gérer les tableaux ou les enregistrements, et ces calculs sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
dry3jwskxg8if7ddg0dk0eyrgoduoif
772034
772031
2026-09-11T20:57:46Z
Mewtow
31375
/* L'adressage indirect à registre pour les pointeurs */
772034
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Et les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage ! Avec eux, le processeur fait le calcul d'adresse de lui-même, sans recourir à des instructions spécialisées. Sans ces modes d'adressage, utiliser des tableaux demande d'utiliser du code automodifiant ou d'autres méthodes qui relèvent de la sorcellerie.
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
p46q6nzemmv55lnksemrmdvwuwmdl3d
772035
772034
2026-09-11T20:58:14Z
Mewtow
31375
/* L'adressage indirect à registre pour les pointeurs */
772035
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas, ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs sont des variables dont le contenu est une adresse mémoire. En clair, les modes d'adressage indirects ne disent pas où se trouve la donnée, mais où se trouve l'adresse de la donnée, un pointeur vers celle-ci.
Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créér des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé. Manipuler des tableaux, des listes chainées, des arbres, ou tout autre structure de donnée un peu complexe, se fait à grand coup de pointeurs. C'est explicite dans des langages comme le C, mais implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions.
===L'adressage indirect à registre pour les pointeurs===
Les modes d'adressages précédents permettaient de localiser une donnée. Nous allons les appeler les ''modes d'adressage directs''. Les '''modes d'adressage indirects''' permettent de localiser non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Concrètement, la plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Mais les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage !
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
mqgob9bfxtyuk4a1zt4dufmmag8ht6j
772047
772035
2026-09-11T22:23:03Z
Mewtow
31375
/* Les modes d'adressage indirects pour les pointeurs */
772047
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents sont appelés les modes d'adressage directs car ils fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Les modes d'adressage qui vont suivre ne sont pas dans ce cas. Ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Les pointeurs sont des variables dont le contenu est une adresse mémoire. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créer des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé.
Il en existe beaucoup : les tableaux, les listes chainées, les arbres, les graphes, les piles, les files, et bien d'autres. Et manipuler une structure de donnée se fait à grand coup de pointeurs, que ce soit explicite dans des langages comme le C, ou implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions. Et au-delà des modes d'adressages pour les pointeurs, il existe aussi des modes d'adressages spécialisés pour certaines structures de données. Précisément, deux structures de données bien précises ont des modes d'adressage dédiés : les enregistrements et les tableaux.
Les modes d'adressages pour les pointeurs sont différents des modes d'adressages vus précédemment, qui sont appelés '''modes d'adressage directs'''. Les modes d'adressages pour les pointeurs permettent de localiser, non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Et ce niveau d'indirection fait qu'ils sont appelés des '''modes d'adressage indirects'''.
La plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Nous allons d'abord voir l'adressage indirect à registre, ainsi que ses nombreuses variantes.
===L'adressage indirect à registre pour les pointeurs===
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Mais les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage !
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
cmwdtdxuyxrv613ytkpdfnxxh5s7prt
772048
772047
2026-09-11T22:25:12Z
Mewtow
31375
/* Les modes d'adressage indirects pour les pointeurs */
772048
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Ils sont appelés '''modes d'adressage directs'''. Les modes d'adressage qui vont suivre ne sont pas dans ce cas. Ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Les pointeurs sont des variables dont le contenu est une adresse mémoire. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créer des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé.
Il en existe beaucoup : les tableaux, les listes chainées, les arbres, les graphes, les piles, les files, et bien d'autres. Et manipuler une structure de donnée se fait à grand coup de pointeurs, que ce soit explicite dans des langages comme le C, ou implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils permettent de localiser, non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions. Au-delà des modes d'adressages pour les pointeurs, il existe aussi des modes d'adressages spécialisés pour certaines structures de données. Précisément, deux structures de données bien précises ont des modes d'adressage dédiés : les enregistrements et les tableaux.
La plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Les modes d'adressages indirect mémoire sont aujourd'hui obsolètes et on les verra à la fin du chapitre.
===L'adressage indirect à registre pour les pointeurs===
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Mais les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage !
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice avant d'ajouter une constante pour repérer la donnée dans la structure. Le mode d'adressage "base + indice + décalage" fait ce calcul automatiquement, directement dans une instruction d'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Et n'oublions pas le registre pour la donnée à écrire ou à lire. En conséquence, les instructions utilisant ce mode d'adressage sont très longues.
Le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un défaut assez majeur de ce mode d'adressage est la mise à l'échelle de l'indice. Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une vraie multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
Rappelons que le but des modes d'adressage indicés est que l'on fait les calculs d'adresse automatiquement, dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un ou deux additionneurs, de quoi faire des décalages simples, pas plus. Intégrer un second circuit de multiplication dédié serait clairement un gâchis de circuits, qui donnerait un gain en performance assez mineur. Pour gérer les tableaux de structures, mieux vaut donc faire les calculs d'adresse avec quelques instructions, pour ensuite faire une instruction LOAD/STORE en adresse indirect à registre (ou Base + indice pour gagner une instruction d'addition).
Les processeurs x86 disposent d'un tel mode d'adressage, mais il est aussi limité que puissant. Très limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8. Deux bits sont utilisés pour encoder par combien décaler l'indice. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Notez que que c'est assez incohérent. La mise à l'échelle gère au maximum des enregistrements de 8 octets, mais le décalage permet de dépasser cette limite. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
ec8eg0j2ltr97eeafqwuxayuv0ljdhu
772053
772048
2026-09-11T22:50:45Z
Mewtow
31375
/* Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements */
772053
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Ils sont appelés '''modes d'adressage directs'''. Les modes d'adressage qui vont suivre ne sont pas dans ce cas. Ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Les pointeurs sont des variables dont le contenu est une adresse mémoire. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créer des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé.
Il en existe beaucoup : les tableaux, les listes chainées, les arbres, les graphes, les piles, les files, et bien d'autres. Et manipuler une structure de donnée se fait à grand coup de pointeurs, que ce soit explicite dans des langages comme le C, ou implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils permettent de localiser, non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions. Au-delà des modes d'adressages pour les pointeurs, il existe aussi des modes d'adressages spécialisés pour certaines structures de données. Précisément, deux structures de données bien précises ont des modes d'adressage dédiés : les enregistrements et les tableaux.
La plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Les modes d'adressages indirect mémoire sont aujourd'hui obsolètes et on les verra à la fin du chapitre.
===L'adressage indirect à registre pour les pointeurs===
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Mais les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage !
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
D'autres processeurs vont encore plus loin : ils sont capables de gérer des tableaux d'enregistrements ! Un tableau d'enregistrement est sur le principe assez simple : on place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique.
Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Par exemple, l'entier est au début de chaque enregistrement et le flottant à la fin, pour tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''.
Le nom de ce mode d'adressage traduit ce qu'il fait. Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
En clair, le calcul se fait en deux temps : on calcule l'adresse du début de la structure avec le mode d'adressage base + indice, avant d'ajouter la position de la donnée dans l'enregistrement. Le '''mode d'adressage "base + indice + décalage" idéal''' fait ce calcul automatiquement, directement lors de l'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun.
Petit détail : le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique. Mais malgré tout, les instructions utilisant ce mode d'adressage sont très longues, vu qu'elles doivent encoder deux registres et deux constantes immédiates. Et c'est là un premier défaut de ce mode d'adressage.
Un autre défaut assez majeur de ce mode d'adressage idéal est la mise à l'échelle de l'indice (la multiplication par la taille de l'enregistrement). Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème.
Rappelons qu'un avantage des modes d'adressage indicés est de faire les calculs d'adresse dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un additionneurs trois-opérandes et de quoi faire des décalages simples, pas plus. Leur ajouter un circuit de multiplication aurait un cout en circuit trop important. De plus, quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
En pratique, le mode d'adressage ''"base + indice + décalage" idéal'' n'est jamais implémenté. A la place, on a un mode d'adressage similaire, où l'indice n'est pas mis à l'échelle avec une multiplication. Il additionne simplement l'adresse de base, l'indice (non-mis à l'échelle), et un décalage. La mise à l'échelle de l'indice est faite avec une instruction de multiplication à part, avant l'instruction LOAD/STORE en mode d'adressage "base + indice + décalage". L'avantage est qu'on économise une constante immédiate, et qu'on n'a pas à faire la multiplication. Un autre avantage est que la taille du décalage n'est pas limitée, on peut utiliser des décalages de grande taille, encodés sur 16 bits, voire plus.
Pour être plus précis, l'indice est bien mis à l'échelle. Mais il est mis à l'échelle comme il le serait avec l'adressage base + indice, à savoir avec un décalage. L'avantage est que l'adressage base + indice reste un cas particulier de ce mode d'adressage. La mise à l'échelle est supportée de base pour les tableaux d'entiers/flottants, mais pas pour les tableaux de structures.
Les processeurs x86 disposent d'un tel mode d'adressage. Il est limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8, ce qui permet de gérer des entiers/flottants de 8, 16, 32 et 64 bits. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées. Les décalages supportés sont donc de grande taille.
Rares sont les processeurs à implémenter le mode d'adressage "base + indice + décalage", même dans sa version sans mise à l'échelle. La raison est que ses défauts sont trop importants. L'implémentation du mode idéal rend les instructions trop longues, sans compter qu'elle demande d'ajouter un circuit de multiplication en plus, avec le cout en circuit que ce implique. L'implémentation sans mise à l'échelle de l'indice a été utilisée sur quelques processeurs, dont les processeurs x86 (voir plus bas). Mais elle est rarement utilisée, surtout de nos jours. La raison est qu'elle rallonge pas mal les instructions. De plus, quitte à devoir faire une multiplication en-dehors du mode d'adressage, autant faire pareil avec l'ajout du décalage
Pour gérer les tableaux de structures sans adressage "base + indice + décalage", ila solution est très simple. Une solution basique est de calculer l'adresse avec des instructions de calcul, puis d'utiliser une instruction LOAD/STORE en adressage indirect à registre. Mais c'est une solution peu optimisée. Une solution plus maligne fait le calcul <math>\text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math> avec des instructions, puis utilise le résultat comme un pseudo-indice. Une instruction LOAD/STORE en adressage Base + indice additionner ce résultat avec l'adresse de base.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
b7hchmxun1uzjz4yfuxnmrwk8hfcxsk
772054
772053
2026-09-11T22:52:36Z
Mewtow
31375
/* Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements */
772054
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Ils sont appelés '''modes d'adressage directs'''. Les modes d'adressage qui vont suivre ne sont pas dans ce cas. Ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Les pointeurs sont des variables dont le contenu est une adresse mémoire. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créer des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé.
Il en existe beaucoup : les tableaux, les listes chainées, les arbres, les graphes, les piles, les files, et bien d'autres. Et manipuler une structure de donnée se fait à grand coup de pointeurs, que ce soit explicite dans des langages comme le C, ou implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils permettent de localiser, non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions. Au-delà des modes d'adressages pour les pointeurs, il existe aussi des modes d'adressages spécialisés pour certaines structures de données. Précisément, deux structures de données bien précises ont des modes d'adressage dédiés : les enregistrements et les tableaux.
La plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Les modes d'adressages indirect mémoire sont aujourd'hui obsolètes et on les verra à la fin du chapitre.
===L'adressage indirect à registre pour les pointeurs===
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Mais les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage !
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
De rares processeurs sont capables de gérer des tableaux d'enregistrements. Un tableau d'enregistrements place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique. Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Le '''mode d'adressage "base + indice + décalage" idéal''' fait ce calcul automatiquement, directement lors de l'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun.
Petit détail : le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique. Mais malgré tout, les instructions utilisant ce mode d'adressage sont très longues, vu qu'elles doivent encoder deux registres et deux constantes immédiates. Et c'est là un premier défaut de ce mode d'adressage.
Un autre défaut assez majeur de ce mode d'adressage idéal est la mise à l'échelle de l'indice (la multiplication par la taille de l'enregistrement). Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème.
Rappelons qu'un avantage des modes d'adressage indicés est de faire les calculs d'adresse dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un additionneurs trois-opérandes et de quoi faire des décalages simples, pas plus. Leur ajouter un circuit de multiplication aurait un cout en circuit trop important. De plus, quitte à utiliser des opérations gourmandes comme des multiplications, autant faire les calculs d'adresse avec de vraies instructions.
En pratique, le mode d'adressage ''"base + indice + décalage" idéal'' n'est jamais implémenté. A la place, on a un mode d'adressage similaire, où l'indice n'est pas mis à l'échelle avec une multiplication. Il additionne simplement l'adresse de base, l'indice (non-mis à l'échelle), et un décalage. La mise à l'échelle de l'indice est faite avec une instruction de multiplication à part, avant l'instruction LOAD/STORE en mode d'adressage "base + indice + décalage". L'avantage est qu'on économise une constante immédiate, et qu'on n'a pas à faire la multiplication. Un autre avantage est que la taille du décalage n'est pas limitée, on peut utiliser des décalages de grande taille, encodés sur 16 bits, voire plus.
Pour être plus précis, l'indice est bien mis à l'échelle. Mais il est mis à l'échelle comme il le serait avec l'adressage base + indice, à savoir avec un décalage. L'avantage est que l'adressage base + indice reste un cas particulier de ce mode d'adressage. La mise à l'échelle est supportée de base pour les tableaux d'entiers/flottants, mais pas pour les tableaux de structures.
Les processeurs x86 disposent d'un tel mode d'adressage. Il est limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8, ce qui permet de gérer des entiers/flottants de 8, 16, 32 et 64 bits. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées. Les décalages supportés sont donc de grande taille.
Rares sont les processeurs à implémenter le mode d'adressage "base + indice + décalage", même dans sa version sans mise à l'échelle. La raison est que ses défauts sont trop importants. L'implémentation du mode idéal rend les instructions trop longues, sans compter qu'elle demande d'ajouter un circuit de multiplication en plus, avec le cout en circuit que ce implique. L'implémentation sans mise à l'échelle de l'indice a été utilisée sur quelques processeurs, dont les processeurs x86 (voir plus bas). Mais elle est rarement utilisée, surtout de nos jours. La raison est qu'elle rallonge pas mal les instructions. De plus, quitte à devoir faire une multiplication en-dehors du mode d'adressage, autant faire pareil avec l'ajout du décalage
Pour gérer les tableaux de structures sans adressage "base + indice + décalage", ila solution est très simple. Une solution basique est de calculer l'adresse avec des instructions de calcul, puis d'utiliser une instruction LOAD/STORE en adressage indirect à registre. Mais c'est une solution peu optimisée. Une solution plus maligne fait le calcul <math>\text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math> avec des instructions, puis utilise le résultat comme un pseudo-indice. Une instruction LOAD/STORE en adressage Base + indice additionner ce résultat avec l'adresse de base.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
lbv78q9zzgqu2bv33z62dqzbbr3n4pb
772055
772054
2026-09-11T22:54:47Z
Mewtow
31375
/* Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements */
772055
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Ils sont appelés '''modes d'adressage directs'''. Les modes d'adressage qui vont suivre ne sont pas dans ce cas. Ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Les pointeurs sont des variables dont le contenu est une adresse mémoire. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créer des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé.
Il en existe beaucoup : les tableaux, les listes chainées, les arbres, les graphes, les piles, les files, et bien d'autres. Et manipuler une structure de donnée se fait à grand coup de pointeurs, que ce soit explicite dans des langages comme le C, ou implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils permettent de localiser, non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions. Au-delà des modes d'adressages pour les pointeurs, il existe aussi des modes d'adressages spécialisés pour certaines structures de données. Précisément, deux structures de données bien précises ont des modes d'adressage dédiés : les enregistrements et les tableaux.
La plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Les modes d'adressages indirect mémoire sont aujourd'hui obsolètes et on les verra à la fin du chapitre.
===L'adressage indirect à registre pour les pointeurs===
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Mais les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage !
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
De rares processeurs sont capables de gérer des tableaux d'enregistrements. Un tableau d'enregistrements place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique. Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Le '''mode d'adressage "base + indice + décalage" idéal''' fait ce calcul automatiquement, directement lors de l'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Une conséquence est que les instructions utilisant ce mode d'adressage sont très longues, vu qu'elles doivent encoder deux registres et deux constantes immédiates. Et c'est là un premier défaut de ce mode d'adressage.
Petit détail : le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un autre défaut de ce mode d'adressage idéal est la mise à l'échelle de l'indice (la multiplication par la taille de l'enregistrement). Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Rappelons que les calculs d'adresse sont réalisés dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un additionneurs trois-opérandes et de quoi faire des décalages simples, pas plus. Leur ajouter un circuit de multiplication aurait un cout en circuit trop important.
En pratique, le mode d'adressage ''"base + indice + décalage" idéal'' n'est jamais implémenté. A la place, on a un mode d'adressage similaire, où l'indice n'est pas mis à l'échelle avec une multiplication. Il additionne simplement l'adresse de base, l'indice (non-mis à l'échelle), et un décalage. La mise à l'échelle de l'indice est faite avec une instruction de multiplication à part, avant l'instruction LOAD/STORE en mode d'adressage "base + indice + décalage". L'avantage est qu'on économise une constante immédiate, et qu'on n'a pas à faire la multiplication. Un autre avantage est que la taille du décalage n'est pas limitée, on peut utiliser des décalages de grande taille, encodés sur 16 bits, voire plus.
Pour être plus précis, l'indice est bien mis à l'échelle. Mais il est mis à l'échelle comme il le serait avec l'adressage base + indice, à savoir avec un décalage. L'avantage est que l'adressage base + indice reste un cas particulier de ce mode d'adressage. La mise à l'échelle est supportée de base pour les tableaux d'entiers/flottants, mais pas pour les tableaux de structures.
Les processeurs x86 disposent d'un tel mode d'adressage. Il est limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8, ce qui permet de gérer des entiers/flottants de 8, 16, 32 et 64 bits. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées. Les décalages supportés sont donc de grande taille.
Rares sont les processeurs à implémenter le mode d'adressage "base + indice + décalage", même dans sa version sans mise à l'échelle. La raison est que ses défauts sont trop importants. L'implémentation du mode idéal rend les instructions trop longues, sans compter qu'elle demande d'ajouter un circuit de multiplication en plus, avec le cout en circuit que ce implique. L'implémentation sans mise à l'échelle de l'indice a été utilisée sur quelques processeurs, dont les processeurs x86 (voir plus bas). Mais elle est rarement utilisée, surtout de nos jours. La raison est qu'elle rallonge pas mal les instructions. De plus, quitte à devoir faire une multiplication en-dehors du mode d'adressage, autant faire pareil avec l'ajout du décalage
Pour gérer les tableaux de structures sans adressage "base + indice + décalage", ila solution est très simple. Une solution basique est de calculer l'adresse avec des instructions de calcul, puis d'utiliser une instruction LOAD/STORE en adressage indirect à registre. Mais c'est une solution peu optimisée. Une solution plus maligne fait le calcul <math>\text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math> avec des instructions, puis utilise le résultat comme un pseudo-indice. Une instruction LOAD/STORE en adressage Base + indice additionner ce résultat avec l'adresse de base.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
jkq68eso0u1ewsvac90afd89oyen728
772056
772055
2026-09-11T22:57:51Z
Mewtow
31375
/* Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements */
772056
wikitext
text/x-wiki
Une instruction n'est pas encodée n'importe comment, la suite de bits associée a une certaine structure. Quelques bits de l’instruction indiquent quelle est l'opération à effectuer : est-ce une instruction d'addition, de soustraction, un branchement inconditionnel, un appel de fonction, une lecture en mémoire, etc. Cette portion de mémoire s'appelle l''''opcode'''.
Il arrive que certaines instructions soient composées d'un opcode, sans rien d'autre : elles ont alors une représentation en binaire qui est unique. Mais la majorité instructions ajoutent des bits pour préciser la localisation des données à manipuler. Une instruction peut alors fournir au processeur ce qu'on appelle une '''référence''', à savoir quelque chose qui permet de localiser une donnée dans la mémoire. Elles indiquent où se situent les opérandes d'un calcul, où stocker son résultat, où se situe la donnée à lire ou écrire, à quel l'endroit brancher pour les branchements.
Reste à savoir quelle est la nature de la référence : est-ce une adresse, un nombre, un nom de registre, de quoi calculer l'adresse ? Chaque manière d’interpréter la partie variable s'appellent un '''mode d'adressage'''. Un mode d'adressage indique au processeur que telle référence est une adresse, un registre, autre chose. Comme nous allons le voir, certaines instructions supportent certains modes d'adressage et pas d'autres. Généralement, les instructions d'accès mémoire possèdent plus de modes d'adressage que les autres, encore que cela dépende du processeur (chose que nous détaillerons dans le chapitre suivant).
Nous verrons dans le chapitre suivant comment sont encodées les instructions à plusieurs opérandes, ce qui dépend fortement du jeu d'instruction utilisé. Mais dans ce chapitre, nous allons nous limiter au cas où une instruction ne manipule qu'une seule opérande. De plus, nous allons nous limiter au cas où l'opérande est chargée dans un registre. La raison est que nous allons nous concentrer sur la description des modes d'adressage proprement dit. L'instruction encode donc un opcode et une référence, pas plus.
==Les modes d'adressages pour les données==
Pour comprendre un peu mieux ce qu'est un mode d'adressage, nous allons voir les modes d'adressage les plus simples qui soient. Ils sont supportés par la majorité des processeurs existants, à quelques détails près que nous élaborerons dans le chapitre suivant. Il s'agit des '''modes d'adressage directs''', qui permettent de localiser directement une donnée dans la mémoire ou dans les registres. Ils précisent dans quel registre, à quelle adresse mémoire se trouve une donnée.
===L'adressage implicite===
Avec l''''adressage implicite''', il n'y a pas besoin de fournir une référence vers l'opérande ! La raison à cela est que l'instruction n'a pas besoin qu'on lui donne la localisation des données d'entrée et « sait » où sont les données. Comme exemple, on pourrait citer une instruction qui met tous les bits du registre d'état à zéro.
===L'adressage inhérent (à registre)===
Avec le mode d''''adressage inhérent''', la partie variable va identifier un registre qui contient la donnée voulue. Ce mode d'adressage demande d'attribuer un '''numéro de registre''' à chaque registre, parfois appelé abusivement un '''nom de registre'''. Pour rappel, ce dernier est un numéro attribué à chaque registre, utilisé pour préciser à quel registre le processeur doit accéder. On parle aussi d'adressage à registre, pour simplifier.
[[File:Adressage inhérent.png|centre|vignette|upright=2|Adressage inhérent]]
===L'adressage immédiat===
Avec l''''adressage immédiat''', la partie variable est une constante : un nombre entier, un caractère, un nombre flottant, etc. Avec ce mode d'adressage, la donnée est placée dans la partie variable et est chargée en même temps que l'instruction.
[[File:Adressage immédiat.png|centre|vignette|upright=2|Adressage immédiat]]
Les constantes en adressage immédiat sont souvent codées sur 8 ou 16 bits. Aller au-delà serait inutile vu que la quasi-totalité des constantes manipulées par des opérations arithmétiques sont très petites et tiennent dans un ou deux octets. La plupart du temps, les constantes sont des entiers signés, c'est à dire qui peuvent être positifs, nuls ou négatifs. Au vu de la différence de taille entre la constante et les registres, les constantes subissent une opération d'extension de signe avant d'être utilisées.
Pour rappel, l'extension de signe convertit un entier en un entier plus grand, codé sur plus de bits, tout en préservant son signe et sa valeur. L'extension de signe des nombres positifs consiste à remplir les bits de poids fort avec des 0 jusqu’à arriver à la taille voulue : c'est la même chose qu'en décimal, où rajouter des zéros à gauche d'un nombre positif ne changera pas sa valeur. Pour les nombres négatifs, il faut remplir les bits à gauche du nombre à convertir avec des 1, jusqu'à obtenir le bon nombre de bits : par exemple, 1000 0000 (-128 codé sur 8 bits) donnera 1111 1111 1000 000 après extension de signe sur 16 bits. L'extension de signe d'un nombre codé en complément à 2 se résume donc en une phrase : il faut recopier le bit de poids fort de notre nombre à convertir à gauche de celui-ci jusqu’à atteindre le nombre de bits voulu.
===L'adressage absolu===
Passons maintenant à l''''adressage absolu''', aussi appelé adressage direct. Avec lui, la partie variable est l'adresse de la donnée à laquelle accéder. Cela permet de lire une donnée directement depuis la mémoire RAM/ROM. Le terme "adressage par adresse" est aussi utilisé. Un défaut de ce mode d'adressage est que l'adresse en question a une taille assez importante, elle augmente drastiquement la taille de l'instruction. Les instructions sont donc soit très longues, sans optimisations.
[[File:Adressage direct.png|centre|vignette|upright=2|Adressage direct]]
Pour raccourcir les instructions, il est possible de ne pas mettre des adresses complètes, mais de retirer les bits de poids forts. L'adressage absolu ne peut alors lire qu'une partie de la mémoire RAM. Il est aussi possible de ne pas encoder les bits de poids faible pour des questions d'alignement mémoire. Les processeurs RISC modernes gèrent parfois le mode d'adressage absolu, ils encodent des adresses sur 16-20 bits pour des processeurs 32 bits. Un exemple plus ancien est le cas de l’ordinateur Data General Nova. Son processeur était un processeur 16 bits, capable d'adresser 64 kibioctets. Il gérait plusieurs modes d'adressages, dont un mode d'adressage absolu avec des adresses codées sur 8 bits. En conséquence, il était impossible d’accéder à plus de 256 octets avec l'adressage absolu, il fallait utiliser d'autres modes d'adressage pour cela. Il s'agit d'un cas extrême.
Une solution un peu différente des précédentes utilise des adresses de taille variable, et donc des instructions de taille variable. Un exemple est celui du mode '''''zero page''''' des processeurs Motorola, notamment des Motorola 6800 et des MOS Technology 6502. Sur ces processeurs, il y avait deux types d'adressages absolus. Le premier mode utilisait des adresses complètes de 16 bits, capables d'adresser toute la mémoire, tout l'espace d'adressage. Le second mode utilisait des adresses de 8 bits, et ne permettait que d'adresser les premiers 256 octets de la mémoire. L'instruction était alors plus courte : avec un opcode de 8bits et des adresses de 8 bits, elle rentrait dans 16 bits, contre 24 avec des adresses de 16 bits. Un autre avantage était que l'accès à ces 256 octets était plus rapide d'un cycle d'horloge, ce qui fait qu'ils étaient monopolisés par le système d'exploitation et les programmes utilisateurs, mais ce n'est pas lié au mode d'adressage absolu proprement dit.
==Les modes d'adressage indirects pour les pointeurs==
Les modes d'adressages précédents fournissent directement une référence vers la donnée, en précisant dans quel registre ou adresse mémoire se trouve la donnée. Ils sont appelés '''modes d'adressage directs'''. Les modes d'adressage qui vont suivre ne sont pas dans ce cas. Ils permettent de localiser une donnée de manière indirecte, en passant par un intermédiaire. D'où leurs noms de '''modes d'adressage indirects'''.
L'intermédiaire en question est ce qui s'appelle un '''pointeur'''. Les pointeurs sont des variables dont le contenu est une adresse mémoire. Il s'agit de fonctionnalités de certains langages de programmation dits bas-niveau (proches du matériel), dont le C. Les pointeurs ont une définition très simple, mais beaucoup d'étudiants la trouvent très abstraite et ne voient pas à quoi ces pointeurs peuvent servir. Pour résumer rapidement, les pointeurs sont utilisées pour manipuler/créer des '''structures de données''', à savoir des regroupements structurées de données plus simples, peu importe le langage de programmation utilisé.
Il en existe beaucoup : les tableaux, les listes chainées, les arbres, les graphes, les piles, les files, et bien d'autres. Et manipuler une structure de donnée se fait à grand coup de pointeurs, que ce soit explicite dans des langages comme le C, ou implicite dans les langages haut-niveau. C'est surtout le cas dans les structures de données où les données sont dispersées dans la mémoire, comme les listes chaînées, les arbres, et toute structure éparse. Localiser les données en question dans la mémoire demande d'utiliser des pointeurs qui pointent vers ces données, qui donnent leur adresse.
[[File:Pointeurs.svg|centre|vignette|upright=1.5|Illustration du concept de pointeur.]]
Il existe des modes d'adressage dédiés pour les pointeurs. Ils permettent de localiser, non pas une donnée, mais un pointeur. Il y a donc un niveau d'indirection en plus. On ne localise pas la donnée elle-même, mais son adresse. Ils sont surtout utilisés par les instructions d'accès mémoire, notamment les instructions LOAD et STORE, ou les instructions MOV sur certains processeurs. Ils ne sont pas censés être utilisés par les instructions de calcul ou les branchements, même s'il y a des exceptions. Au-delà des modes d'adressages pour les pointeurs, il existe aussi des modes d'adressages spécialisés pour certaines structures de données. Précisément, deux structures de données bien précises ont des modes d'adressage dédiés : les enregistrements et les tableaux.
La plupart des modes d'adressages direct ont un mode d'adressage indirect similaire. Par exemple, le mode d'adressage à registre/inhérent indique le registre qui contient la donnée, sa version indirecte indique le registre qui contient le pointeur vers cette donnée. Idem avec le mode d'adressage absolu : sa version directe fournit l'adresse de la donnée, sa version indirecte fournit l'adresse du pointeur.
Par contre, il n'est pas possible de prendre tous les modes d'adressage précédents, et d'en faire des modes d'adressage indirects. L'adressage implicite d'un pointeur existe, mais est assez rare et ne marche que dans des circonstances très précises. Quand à l'adressage immédiat, il n'a pas d'équivalent indirect, même si on peut interpréter l'adressage absolu comme tel. Si on regarde les possibilités, un pointeur peut être soit dans un registre, soit en mémoire RAM, ce qui donne deux classes de modes d'adressages indirect : à registre et mémoire. Les modes d'adressages indirect mémoire sont aujourd'hui obsolètes et on les verra à la fin du chapitre.
===L'adressage indirect à registre pour les pointeurs===
Avec l''''adressage indirect à registre''', le pointeur est stockée dans un registre. Le registre en question contient donc un l'adresse de la donnée à lire/écrire, celle qui pointe vers la donnée à lire/écrire. Lors de l'exécution de l'instruction, le pointeur dans le registre est envoyé sur le bus d'adresse, et la donnée est récupérée sur le bus de données.
Ici, la partie variable de l'instruction identifie un registre contenant l'adresse de la donnée voulue. La différence avec le mode d'adressage inhérent vient de ce qu'on fait de ce nom de registre : avec le mode d'adressage inhérent, le registre indiqué dans l'instruction contiendra la donnée à manipuler, alors qu'avec le mode d'adressage indirect à registre, le registre contiendra l'adresse de la donnée.
[[File:Adressage indirects à registre.png|centre|vignette|upright=2|Adressage indirects à registre]]
L'adressage indirect à registre gère les pointeurs nativement, mais pas plus. Pour gérer des structures de données, comme des tableaux ou des enregistrements, il faut faire des calculs d'adresse, qui sont réalisés par des instructions de calcul normales. Le mode d'adressage indirect à registre ne gère pas de calculs d'adresse en lui-même. Mais les modes d'adressages qui vont suivre intègrent ce mode de calcul directement dans le mode d'adressage !
===Les modes d'adressage pour les enregistrements===
Après avoir vu les modes d'adressage pour les pointeurs en général, nous allons voir des modes d'adressage spécialisés pour les structures de données les plus simples qui soit. Ces structures de données sont appelés des '''enregistrements''', ou encore des structures en langage C. Elles regroupent plusieurs données simples, comme des entiers, des adresses, des flottants, des caractères, etc. Par exemple, on peut regrouper deux entiers et un flottant dans une structure, qui regroupe les deux.
Un exemple serait le stockage d'une date. La date est composée d'un jour, du mois et de l'année. Une solution simple, pas du tout optimisée, serait d'utiliser trois entiers pour cela : un pour le jour, un autre pour le mois (de 1 à 12), et un troisième pour l'année. Il est alors possible de définir un enregistrement DATE qui regroupe ces trois entiers. Les trois entiers sont placés les uns à la suite des autres dans la mémoire RAM. Tous les enregistrements DATE ont la même organisation interne, l'entier JOUR est placé à la même place dans tous les enregistrements DATE.
Comme autre exemple, imaginez que vous ayez un programme qui fasse des calculs de géométrie dans l'espace. Par exemple, le moteur physique d'un jeu vidéo, ou son moteur graphique. Ils doivent connaitre la position dans l'espace des objets qu'ils manipulent, comme la position de la caméra, des ennemis à tuer, ou bien d'autres choses. La position est typiquement encodée en utilisant trois coordonnées x, y et z, qui sont chacune un flottant. De tels programmes définissent alors un enregistrement POSITION qui regroupe trois flottants.
Pour résumer, les enregistrements regroupent plusieurs données, généralement une petite dizaine d'entiers/flottants/adresses. Mais le processeur ne peut pas manipuler ces enregistrements directement. Il est obligé de manipuler les entiers/flottants/caractères qui le constituent, un par un. Pour cela, il doit calculer leur adresse. Et les modes d'adressage qui vont suivre permettent de le faire automatiquement.
Il est possible de localiser un enregistrement dans la mémoire RAM, avec un pointeur vers celui-ci. Le pointeur mémorise l'adresse de l'enregistrement. Reste à localiser une donnée dans un enregistrement et c'est un jeu d'enfant, car une donnée a une place prédéterminée dans un enregistrement. Un élément est donc a une distance fixe du début de l'enregistrement, qui est connu à la compilation. En clair, l'adresse d'un élément se calcule en ajoutant une constante à un pointeur. Et c'est ce que fait le mode d''''adressage base + décalage'''. Il spécifie un registre pour le pointeur et une constante à ajouter.
[[File:Base + offset.png|centre|vignette|upright=2|Base + offset]]
Quand on dit que l'addition est réalisée automatiquement, cela peut vouloir dire deux choses. Une première implémentation n'utilise qu'un seul additionneur, qui est utilisé à la fois pour les instructions d'addition et les calculs d'adresse implicite. L'addition pointeur + décalage est réalisé dans cet additionneur, au même titre que les instructions d'addition. Une seconde implémentation utilise des circuits dédiés pour les calculs d'adresse, séparés de l'additionneur qui fait les additions "normales". Il a donc une unité de calcul pour les instructions d'additions, et une unité de calcul d'adresse pour les instructions LOAD/STORE. L'intérêt est que le calcul d'adresse est bien plus rapide, sans compter que la conception du processeur est plus simple. Mais cela coute un peu en transistors.
Dans les deux cas, l'intérêt est que l'on a pas à utiliser d'instructions pour faire le calcul d'adresse. Au lieu d'utiliser une addition avant l'instruction LOAD/STORE, on n'utilise qu'une seule instruction LOAD/STORE qui fait le calcul d'adresse automatiquement.
===Les modes d'adressage indicés pour les tableaux===
Un autre type de structure de donnée très utilisée est les '''tableaux'''. Ce sont des structures de données où plusieurs données de même type sont placées les unes à la suite des autres en mémoire. Par exemple, on peut placer 105 entiers les uns à la suite des autres en mémoire, placer un millier de nombres flottants simple précision les uns à la suite des autres, etc. Un exemple est le stockage du texte : on peut stocker du texte en mémoire RAM en utilisant un tableau de caractères, chacun codé sur un octet/''byte''.
Toute donnée dans le tableau se voit attribuer un '''indice''', un nombre entier qui indique la position de la donnée dans le tableau. Attention : les indices commencent à zéro, et non à 1, ce qui fait que la première donnée du tableau porte l'indice 0 ! L'indice dit si on veut la première donnée (indice 0), la deuxième (indice 1), la troisième (indice 2), etc.
[[File:Tableau à une dimension.png|centre|vignette|upright=2|Tableau]]
Le tableau commence à une adresse appelée l''''adresse de base''', qui est mémorisée dans un pointeur. Localiser un entier dans le tableau demande de faire des calculs avec le pointeur et l'indice. Intuitivement, on se dit qu'il suffit d'additionner le pointeur avec l'indice. Mais ce serait oublier qu'il faut tenir compte de la taille de la donnée.
Par exemple, prenons un tableau d'entier, chacun codé sur 32 bits (4 octets). Le tableau commence à l'adresse A, le second entier est à l'adresse A + 4, le troisième entier est à l'adresse A + 8, le quatrième à l'adresse A + 12, etc. En regardant bien, on remarque que l'adresse final se calcule en multipliant l'indice par 4, avant d'ajouter l'adresse de base.
{|class="wikitable"
|-
! Adresses !! Tableau
|-
| '''A''' || rowspan="4" | Premier entier
|-
| A + 1
|-
| A + 2
|-
| A + 3
|-
| '''A + 4''' || rowspan="4" | Second entier
|-
| A + 5
|-
| A + 6
|-
| A + 7
|-
| '''A + 8''' || rowspan="4" | Troisième entier
|-
| A + 9
|-
| A + 10
|-
| A + 11
|-
| '''A + 12''' || rowspan="4" | Quatrième entier
|-
| A + 13
|-
| A + 14
|-
| A + 15
|-
| ... || ...
|-
| '''A + indice * 4''' || rowspan="4" | Énième entier
|-
| A + indice * 4 + 1
|-
| A + indice * 4 + 2
|-
| A + indice * 4 + 3
|}
Et cela se généralise pour des données qui ne font pas 4 octets, mais aussi 1 octet, 2 octets, 8 octets, etc. Il suffit alors de remplacer 4 par la taille de la donnée en octets. Le calcul de l'adresse d'une donnée dans le tableau se fait en multipliant l'indice par la taille de la donnée, puis en additionnant le pointeur. De nombreux modes d'adressage permettent de faire ce calcul directement, comme nous allons le voir.
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Les '''modes d'adressage indicés''' font ce calcul d'adresse automatiquement, sous certaines conditions. Pour être plus précis, les modes d'adressage indicés font ce calcul d'adresse pour les tableaux de nombres entiers, de nombres flottants et les tableaux de caractères (du texte, en gros). Le cas des tableaux d'enregistrements sera vu à part. L'intérêt de se concentrer sur des entiers/flottants/caractères est que la taille de ces données est connue à l'avance et qu'elle est une puissance de deux. Accéder à un élément d'un tableau demande de préciser deux choses : l'adresse de base et l'indice de l’élément voulu. La gestion de la taille de la donnée est simplifiée.
Les modes d'adressage indicés portent ce nom car ils gèrent automatiquement l'indice. L'indice est placé dans un registre, disons un registre général. Utiliser un indice constant est possible, mais le mode d'adressage est alors équivalent à l'adressage base + décalage vu précédemment. En effet, avec un indice constant, le calcul d'adresse se simplifie grandement. Après simplification, le calcul d'adresse revient à additionner une constante à une adresse de base, ce qui est équivalent à l'adressage base + décalage.
L'indice est donc dans un registre. Mais qu'en est-il de l'adresse de base ? La possibilité la plus simple est de mettre celle-ci dans un pointeur, et donc dans un registre. Le résultat est le mode d''''adressage base + indice''', où l'adresse de base et l'indice sont tous deux dans des registres généraux, ce qui fait que ce mode d'adressage précise deux numéros/noms de registre. En clair, indice et pointeur sont localisés via adressage inhérent (à registre). Le calcul de l'adresse est effectué automatiquement par le processeur.
[[File:Base + Index.png|centre|vignette|upright=2|Base + Index]]
Le mode d'adressage base + indice est surtout utilisé pour les tableaux simples, qui contiennent un caractère, un entier ou un flottant. En clair, des données que le processeur peut accéder en une seule instruction LOAD ou STORE. Le calcul de l'adresse doit cependant tenir compte de la taille de la donnée. Rappelons que le calcul exact est celui-ci :
: <math>\text{Adresse finale} = \text{Adresse de base} + \text{Indice} \times \text{Taille de la donnée}</math>
Un problème est que la taille de la donnée n'est pas la même selon que l'on parle d'un caractère, d'un entier, d'un flottant simple précision ou d'un flottant double précision. Et ne parlons pas du cas où le processeur supporte des entiers de différentes tailles, comme des entiers de 16, 32 ou 64 bits. Pour exploiter l'adressage base + indice, il faut donc préciser la taille de la donnée. Et il y a plusieurs solutions pour cela.
La première solution ne tient pas compte de la taille de la donnée. L'adresse de base est additionnée avec l'indice, rien de plus. Le programme multiplier l'indice par la taille de la donnée lui-même. Lors du parcours d'un tableau, le programmeur doit incrémenter/décrémenter l'indice en tenant compte de la taille de la donnée. Par exemple, pour un tableau d'entiers de 4 octets chacun, l'indice doit être incrémenté/décrémenté par pas de 4. L'avantage est que cela permet de gérer des données de taille assez variées, et notamment les tableaux de structures.
Une seconde solution utilise une instruction différente pour chaque taille de donnée possible. Par exemple, il y a une instruction LOAD pour les caractères, une pour les entiers 16 bits, une pour les entiers 32 bits, une pour les entiers 64 bits, une pour les flottants simple précision (32 bits), une pour les flottants double précision (64 bits), etc. Idem pour les instructions STORE. Cette solution est utilisée sur le jeu d'instruction AArch64 d'ARM.
La troisième solution encode la taille de la donnée dans l'instruction. L'instruction contient quelques bits qui précisent la taille de la donnée, en octets. Le nombre de bits est souvent petit, à peine 2-3 bits, ce qui permet de gérer des données basiques de 1, 2, 4, 8 octets, guère plus. Le programme gère alors les indices normalement, il les incrémente/décrémente par pas de 1, l'indice est automatiquement multiplié par la taille de la donnée, il n'y a pas de multiplication dans le code du programme.
La quatrième solution n'encode pas la taille de la donnée en octet, afin de gagner un tout petit peu en rapidité. Pour rappel, l'indice est multipliée par la taille de la donnée. Or, les données supportées par un processeur font toutes 1, 2, 4, 8, 16 octets, qui est toujours une puissance de deux. En conséquence, la multiplication avec l'indice est remplacée par un décalage. L'indice est décalé de 1, 2, 3, 4 rangs vers la gauche. L'idée est alors de ne pas encoder la taille de la donnée, mais le nombre de rands du décalage. On parle alors de mode d'adressage '''indicé mis à l'échelle''' ('''''scaled''' index adressing'').
Le mode d'adressage base + indice a parfois une variante qui permet de vérifier qu'on ne « déborde » pas du tableau, qu'on ne calcule pas une adresse en dehors du tableau, à cause d'un indice erroné, par exemple. Accéder à l’élément 25 d'un tableau de seulement 5 éléments n'a pas de sens et est souvent signe d'une erreur. Pour cela, l'instruction peut prendre deux opérandes supplémentaires (qui peuvent être constants ou placés dans deux registres). L'instruction BOUND sur le jeu d'instruction x86 en est un exemple. Si cette variante n'est pas supportée, on doit faire ces vérifications à la main.
Le mode d''''adressage absolu indexé''' (''indexed absolute'', ou encore ''base+offset'') est une variante de l'adressage précédent, qui est spécialisée pour les tableaux dont l'adresse de base est fixée une fois pour toute, elle est connue à la compilation. Les tableaux de ce genre sont assez rares : ils correspondent aux tableaux de taille fixe, déclarée dans la mémoire statique.
L'adresse de base du tableau est alors précisée via une adresse mémoire et non un nom de registre. En clair, l'adresse de base est précisée par adressage absolu, alors que l'indice est précisé par adressage à registre. À partir de ces deux données, l'adresse de l’élément du tableau est calculée, envoyée sur le bus d'adresse, et l’élément est récupéré. Là encore, il faut préciser la taille de la donnée pour multiplier l'indice, le mettre à l'échelle. Pour ce faire, on peut réutiliser les solutions utilisées pour l'adressage base + indice sont aussi utilisée avec l'adresse absolu indéxé.
[[File:Indexed Absolute.png|centre|vignette|upright=2|Indexed Absolute]]
Pour les deux modes d'adressage précédent, l'indice est mémorisé dans un registre général, éventuellement un registre entier. Mais il a existé des processeurs qui utilisaient des '''registres d'indice''' spécialisés dans les indices de tableaux. Les processeurs en question sont des processeurs assez anciens, appelés des ''processeurs à accumulateurs'', qui sont assez rares de nos jours. Un chapitre entier sera dédié à ces processeurs, aussi je laisse les registres d'indice sous silence pour le moment.
===Les modes d'adressage pour le parcours d'un tableau===
Pour faciliter le parcours d'un tableau, il existe des variantes de l'adressage indirect à registre, qui incrémentent ou décrémentent automatiquement le pointeur à chaque lecture/écriture. Il s'agit des modes d''''adressages indirect avec auto-incrément''' (''register indirect autoincrement'') et '''indirect avec auto-décrément''' (''register indirect autodecrement''). Avec eux, le contenu du registre est incrémenté/décrémenté d'une valeur fixe automatiquement. Cela permet de passer directement à l’élément suivant ou précédent dans un tableau.
[[File:Adressage indirect à registre post-incrémenté.png|centre|vignette|upright=2|Adressage indirect à registre post-incrémenté]]
Là encore, la taille des données est prise en compte. le pointeur n'est pas forcément incrémenté de +1 (ou décrémenté de -1), il peut être incrémenté de 2, 3, 4, 5, 6, etc. Il y a une limite cependant, et l'incrément est souvent fixé une fois pour toute par l'instruction.
En théorie, il y a une différence entre les deux modes d'adressages. Avec l'adressage indirect avec auto-incrément, l'incrémentation se fait APRES l'envoi de l'adresse, après la lecture/écriture. On effectue l'accès mémoire avec le pointeur, avant d'incrémenter le pointeurs. Par contre, pour l'adresse indirect avec auto-décrément, on décrémente le pointeur AVANT de faire l'accès mémoire. Les deux comportements semblent incohérents, mais ils sont en réalité très intuitifs quand on sait comment se fait le parcours d'un tableau.
Le parcours d'un tableau du début vers la fin commence à l'adresse de base du tableau, celle de son premier élément. Aussi, si on place l'adresse de base du tableau dans un pointeur, on accède à l'adresse, puis ensuite on incrémente le tout. Pour le parcours en sens inverse, on commence à l'adresse de fin du tableau, celle à laquelle on quitte le tableau. Ce n'est pas l'adresse du dernier élément, mais l'adresse qui se situe immédiatement après. Pour obtenir l'adresse du dernier élément, on doit soustraire la taille de l'élément à l'adresse initiale. En clair, on décrémente l'adresse avant d'y accéder.
[[File:Adresses lors du parcours d'un tableau.png|centre|vignette|upright=2|Adresses lors du parcours d'un tableau]]
: Pour ceux qui savent déjà ce qu'est une exception matérielle : les deux modes d'adressage précédents posent des problèmes avec les exceptions matérielles. Le problème vient du fait que l'accès mémoire peut générer une exception matérielle, comme un problème de mémoire virtuelle ou autres. Dans ce cas, l'exception matérielle est gérée par une routine d'interruption, puis la routine se termine et l'instruction cause est ré-exécutée. Mais la ré-exécution doit tenir compte du fait que le pointeur initial a été incrémenté/décrémentée, et qu'il faut donc le faire revenir à sa valeur initiale. Quelques machines ont eu des problèmes d'implémentation de ce genre, notamment le DEC VAX et le Motorola 68000.
===Le mode d'adressage base + indice + décalage pour les tableaux d'enregistrements===
De rares processeurs sont capables de gérer des tableaux d'enregistrements. Un tableau d'enregistrements place plusieurs enregistrements l'un après l'autre en mémoire RAM. Un point important est que tous les enregistrements doivent avoir la même taille et leur organisation interne est identique. Par exemple, il est possible de regrouper plusieurs enregistrements comprenant chacun un entier et un flottant, à condition que l'entier et la flottant aient la même place dans tous les enregistrements. Impossible d'avoir deux enregistrements où l'entier et le flottant sont à des places différentes. De la même manière, il est impossible d'avoir un enregistrement contenant deux entiers et un autre avec deux flottants dans un même tableau (sauf usage d'unions et autre sorcellerie, mais passons cela sous silence).
De rares processeurs CISC peuvent gérer ce genre de tableaux d'enregistrements nativement. A savoir que l'instruction LOAD ou STORE peuvent calculer l'adresse d'un élément automatiquement, sans avoir à utiliser d'instruction d'addition, de décalage ou de multiplication. Ce genre de prouesse est possible grâce au mode d''''adressage base + indice + décalage'''. Le nom de ce mode d'adressage traduit ce qu'il fait.
Pour rappel, l'adresse d'un enregistrement se calcule comme pour tout élément de tableau, avec la formule mathématique qu'on a vu plus haut :
: <math>\text{Adresse de l'enregistrement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement}</math>
Maintenant, pour avoir l'adresse d'un élément dans un enregistrement, il faut ajouter la position de cet élément dans l'enregistrement, ce qui donne l'adresse complète :
: <math>\text{Adresse d'un élement} = \text{Adresse de base du tableau} + \text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math>
Le '''mode d'adressage "base + indice + décalage" idéal''' fait ce calcul automatiquement, directement lors de l'accès mémoire. Il a besoin pour cela de : deux registres, deux constantes immédiates. Un registre pour l'adresse de base, un autre pour l'indice. La taille de l'enregistrement et le décalage sont encodées avec une constante immédiate chacun. Une conséquence est que les instructions utilisant ce mode d'adressage sont très longues, vu qu'elles doivent encoder deux registres et deux constantes immédiates. Et c'est là un premier défaut de ce mode d'adressage.
Petit détail : le décalage et la taille de l'enregistrement doivent être encodés sur le même nombre de bits. Par exemple, si on veut gérer des enregistrements de 256 octets maximum, une donnée peut se trouver dans n'importe quel octet, ce qui fait 256 positions différentes, correspondant à des décalages compris entre 0 et 255. Les enregistrements de plus de 256 octets sont rares, ce qui fait qu'on peut encoder décalage et taille sur 6 à 8 bits sans que cela pose problème en pratique.
Un autre défaut de ce mode d'adressage idéal est la mise à l'échelle de l'indice (la multiplication par la taille de l'enregistrement). Les enregistrements peuvent avoir des tailles arbitraires, avec des enregistrements de 25 octets, d'autres de 13, d'autres de 12, etc. La mise à l'échelle de l'indice demande donc de faire une multiplication, qu'on ne peut pas remplacer par un décalage. Et cela pose problème. Rappelons que les calculs d'adresse sont réalisés dans des circuits dédiés, qui doivent rester assez simples. Par simples, on veut dire qu'ils doivent se limiter à un additionneurs trois-opérandes et de quoi faire des décalages simples, pas plus. Leur ajouter un circuit de multiplication aurait un cout en circuit trop important.
En pratique, le mode d'adressage ''"base + indice + décalage" idéal'' n'est jamais implémenté. A la place, on a un mode d'adressage similaire, où l'indice n'est pas mis à l'échelle avec une multiplication. Il additionne simplement l'adresse de base, l'indice (non-mis à l'échelle), et un décalage. La mise à l'échelle de l'indice est faite avec une instruction de multiplication à part, avant l'instruction LOAD/STORE en mode d'adressage "base + indice + décalage". L'avantage est qu'on économise une constante immédiate, et qu'on n'a pas à faire la multiplication. Un autre avantage est que la taille du décalage n'est pas limitée, on peut utiliser des décalages de grande taille, encodés sur 16 bits, voire plus.
Pour être plus précis, l'indice est bien mis à l'échelle. Mais il est mis à l'échelle comme il le serait avec l'adressage base + indice, à savoir avec un décalage. L'avantage est que l'adressage base + indice reste un cas particulier de ce mode d'adressage. La mise à l'échelle est supportée de base pour les tableaux d'entiers/flottants, mais pas pour les tableaux de structures.
Les processeurs x86 disposent d'un tel mode d'adressage. Il est limité pour la mise à l'échelle de l'indice, mais très puissant pour le décalage, trop puissant pourrait-on dire. Pour la mise à l'échelle de l'indice, celui-ci peut être multiplié par 1, 2, 4 ou 8, ce qui permet de gérer des entiers/flottants de 8, 16, 32 et 64 bits. Pour le décalage, celui-ci est encodé sur 8 bits, ce qui est beaucoup plus puissant. Il est même possible d'utiliser des décalages de 16 bits, 32 bits ou 64 bits, mais dans des circonstances plus limitées. Les décalages supportés sont donc de grande taille.
Rares sont les processeurs à implémenter le mode d'adressage "base + indice + décalage", même dans sa version sans mise à l'échelle. La raison est que ses défauts sont trop importants. L'implémentation idéale n'a jamais été utilisée : elle rend les instructions trop longues, sans compter qu'elle demande d'ajouter un circuit de multiplication dont le cout en circuit est prohibitif. L'implémentation sans mise à l'échelle de l'indice a été utilisée sur quelques processeurs, dont les processeurs x86, . Mais elle est rarement utilisée, surtout de nos jours, car elle rallonge trop les instructions. Seuls les processeurs avec des instructions de taille variable peuvent se permettre de l'utiliser. De plus, quitte à devoir faire une multiplication en-dehors du mode d'adressage, autant faire pareil avec l'ajout du décalage.
Pour gérer les tableaux de structures sans adressage "base + indice + décalage", la solution est très simple. Une solution basique est de calculer l'adresse avec des instructions de calcul, puis d'utiliser une instruction LOAD/STORE en adressage indirect à registre. Mais c'est une solution peu optimisée. Une solution plus maligne fait le calcul <math>\text{Indice} \times \text{Taille d'un enregistrement} + \text{décalage}</math> avec des instructions, puis utilise le résultat comme un pseudo-indice. Une instruction LOAD/STORE en adressage Base + indice additionner ce résultat avec l'adresse de base.
==Les modes d'adressage pour les branchements==
Les modes d'adressage des branchements permettent de donner l'adresse de destination du branchement, l'adresse vers laquelle le processeur reprend son exécution si le branchement est pris. Les instructions de branchement peuvent avoir plusieurs modes d'adressages : implicite, direct, relatif ou indirect. Suivant le mode d'adressage, l'adresse de destination est
* soit dans l'instruction elle-même (adressage direct) ;
* soit dans un registre du processeur (branchement indirect) ;
* soit calculée à l’exécution (relatif) ;
* soit précisée de manière implicite.
Avec un '''branchement direct''', l'opérande est simplement l'adresse de l'instruction à laquelle on souhaite reprendre. Il s'agit d'une sorte d'équivalent à l'adressage immédiat/absolu, mais pour les branchements.
[[File:Branchement direct.png|centre|vignette|upright=2|Branchement direct.]]
Les '''branchements relatifs''' permettent de localiser la destination d'un branchement par rapport à l'instruction en cours. Cela permet de dire « le branchement est 50 instructions plus loin ». Avec eux, l'opérande est un nombre qu'il faut ajouter au registre d'adresse d'instruction pour tomber sur l'adresse voulue. On appelle ce nombre un décalage (offset).
[[File:Branchement relatif.png|centre|vignette|upright=2|Branchement relatif]]
Avec les '''branchements indirects''', l'adresse vers laquelle on souhaite brancher peut varier au cours de l’exécution du programme. Il s'agit d'une sorte d'équivalent à l'adressage indirect à registre, mais pour les branchements. Ces branchements sont souvent camouflés dans des fonctionnalités un peu plus complexes des langages de programmation (pointeurs sur fonction, chargement dynamique de bibliothèque, structure de contrôle <code>switch</code>, et ainsi de suite). Avec ces branchements, l'adresse vers laquelle on veut brancher est stockée dans un registre.
[[File:Branchement indirect.png|centre|vignette|upright=2|Branchement indirect]]
Les branchements implicites se limitent aux instructions de retour de fonction, qu'on abordera dans quelques chapitres. L'instruction SKIP est équivalente à un branchement relatif dont le décalage est de 2. Il n'est pas précisé dans l'instruction, mais est implicite.
==Les modes d'adressage pour les conditions/tests==
Pour rappel, les instructions à prédicats et les branchements s’exécutent si une certaine condition est remplie. Pour rappel, on peut faire face à deux cas. Dans le premier, le branchement et l'instruction de test sont fusionnés en une seule instruction. Dans le second, la condition en question est calculée par une instruction de test séparée du branchement. Dans les deux cas, on doit préciser quelle est la condition qu'on veut vérifier. Cela peut se faire de différentes manières, mais la principale est de numéroter les différentes conditions et d'incorporer celles-ci dans l'instruction de test ou le branchement.
Un second problème survient quand on a une instruction de test séparée du branchement. Le résultat de l'instruction de test est mémorisé soit dans un registre de prédicat (un registre de 1 bit qui mémorise le résultat d'une instruction de test), soit dans le registre d'état. Les instructions à prédicats et les branchements doivent alors préciser où se trouve le résultat de la condition adéquate, ce qui demande d'utiliser un mode d'adressage spécialisé.
Pour résumer peut faire face à trois possibilités :
* soit le branchement et le test sont fusionnés et l'adressage est implicite ;
* soit l'instruction de branchement doit préciser le registre à prédicat adéquat ;
* soit l'instruction de branchement doit préciser le bon bit dans le registre d'état.
===L'adressage des registres à prédicats===
La première possibilité est celle où les instructions de test écrivent leur résultat dans un registre à prédicat, qui est ensuite lu par le branchement. De tels processeurs ont généralement plusieurs registres à prédicats, chacun étant identifié par un nom de registre spécialisé. Les noms de registres pour les registres à prédicats sont séparés des noms des registres généraux/entiers/autres. Par exemple, on peut avoir des noms de registre à prédicats codés sur 4 bits (16 registres à prédicats), alors que les noms pour les autres registres sont codés sur 8 bits (256 registres généraux).
La distinction entre les deux se fait sur deux points : leur place dans l'instruction, et le fait que seuls certaines instructions utilisent les registres à prédicats. Typiquement, les noms de registre à prédicats sont utilisés uniquement par les instructions de test et les branchements. Ils sont utilisés comme registre de destination pour les instructions de test, et comme registre source (à lire) pour les branchements et instructions à prédicats. De plus, ils sont placés à des endroits très précis dans l'instruction, ce qui fait que le décodeur sait identifier facilement les noms de registres à prédicats des noms des autres registres.
===L'adressage du registre d'état===
La seconde possibilité est rencontrée sur les processeurs avec un registre d'état. Sur ces derniers, le registre d'état ne contient pas directement le résultat de la condition, mais celle-ci doit être calculée par le branchement ou l'instruction à prédicat. Et il faut alors préciser quels sont le ou les bits nécessaires pour connaitre le résultat de la condition. En conséquence, cela ne sert à rien de numéroter les bits du registre d'état comme on le ferais avec les registres à prédicats. A la place, l'instruction précise la condition à tester, que ce soit l'instruction de test ou le branchement. Et cela peut être fait de manière implicite ou explicite.
La première possibilité est d'indiquer explicitement la condition à tester dans l'instruction. Pour cela, les différentes conditions possibles sont numérotées, et ce numéro est incorporé dans l'instruction de branchement. L'instruction de branchement contient donc un opcode, une adresse de destination ou une référence vers celle-ci, puis un numéro qui indique quelle condition tester.
Un exemple assez intéressant est l'ARM1, le tout premier processeur de marque ARM. Sur l'ARM1, le registre d'état est mis à jour par une opération de comparaison, qui est en fait une soustraction déguisée. L'opération de comparaison soustrait deux opérandes A et B, met à jour le registre d'état en fonction du résultat, mais n'enregistre pas ce résultat dans un registre et s'en débarrasse.
Le registre d'état est un registre contenant 4 bits appelés N, Z, C et V : Z indique que le résultat de la soustraction vaut 0, N indique qu'il est négatif, C indique que le calcul a donné un débordement d'entier non-signé, et V indique qu'un débordement d'entier signé. Avec ces 4 bits, on peut obtenir 16 conditions possibles, certaines indiquant que les deux nombres sont égaux, différents, que l'un est supérieur à l'autre, inférieur, supérieur ou égal, etc. L'instruction précise laquelle de ces 16 conditions est nécessaire : l'instruction s’exécute si la condition est remplie, ne s’exécute pas sinon. Voici les 16 conditions possibles :
{|class="wikitable"
|-
! Code fournit par l’instruction
! Test sur le registre d'état
! Interprétation
|-
! 0000
| Z = 1
| Les deux nombres A et B sont égaux
|-
! 0001
| Z = 0
| Les deux nombres A et B sont différents
|-
! 0010
| C = 1
| Le calcul arithmétique précédent a généré un débordement non-signé
|-
! 0011
| C = 0
| Le calcul arithmétique précédent n'a pas généré un débordement non-signé
|-
! 0100
| N = 1
| Le résultat est négatif
|-
! 0101
| N = 0
| Le résultat est positif
|-
! 0110
| V = 1
| Le calcul arithmétique précédent a généré un débordement signé
|-
! 0111
| V = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! 1000
| C = 1 et Z = 0
| A > B si A et B sont non-signés
|-
! 1001
| C = 0 ou Z = 1
| A <= B si A et B sont non-signés
|-
! 1010
| N = V
| A >= B si on calcule A - B
|-
! 1011
| N != V
| A < B si on calcule A - B
|-
! 1100
| Z = 0 et ( N = V )
| A > B si on calcule A - B
|-
! 1101
| Z = 1 ou ( N = 1 et V = 0 ) ou ( N = 0 et V = 1 )
| A <= B si on calcule A - B
|-
! 1110
| colspan="2" | L'instruction s’exécute toujours (pas de prédication).
|-
! 1111
| colspan="2" | L'instruction ne s’exécute jamais (NOP).
|}
La seconde possibilité est celle de l'adressage implicite du registre d'état. C'est le cas sur les processeurs x86, où il y a plusieurs instructions de branchements, chacune calculant une condition à partir des bits du registre d'état. Le registre d'état est similaire à celui de l'ARM1 vu plus haut. Le registre d'état des CPU x86 contient 5 bits : ZF indique que le résultat de la soustraction vaut 0, SF indique son signe, CF est le bit de retenue et de débordement non-signé, OF le bit de débordement signé, et PF le bit qui donne la parité du résultat. Il existe plusieurs branchements, certains testant un seul bit du registre d'état, d'autres une combinaison de plusieurs bits.
{|class="wikitable"
|-
! Instruction de branchement
! Bit du registre d'état testé
! Condition testée si on compare deux nombres A et B avec une instruction de test
|-
! JS (Jump if Sign)
| N = 1
| Le résultat est négatif
|-
! JNS (Jump if not Sign)
| N = 0
| Le résultat est positif
|-
! JO (Jump if Overflow)
| SF = 1 ou
| Le calcul arithmétique précédent a généré un débordement signé
|-
! JNO (Jump if Not Overflow)
| SF = 0
| Le calcul arithmétique précédent n'a pas généré de débordement signé
|-
! JNE (Jump if Not equal)
| Z = 1
| Les deux nombres A et B sont égaux
|-
! JE (Jump if Equal)
| Z = 0
| Les deux nombres A et B sont différents
|-
! JB (Jump if below)
| C = 1
| A < B, avec A et B non-signés
|-
! JAE (Jump if Above or Equal)
| C = 0
| A >= B, avec A et B non-signés
|-
! (JBE) Jump if below or equal
| C = 1 ou Z = 0
| A >= B si A et B sont non-signés
|-
! JA (Jump if above)
| C = 0 et Z = 0
| A > B si A et B sont non-signés
|-
! JL (Jump if less)
| SF != OF
| si A < BA et B sont signés
|-
! JGE (Jump if Greater or Equal)
| SF = OF
| si A >= BA et B sont signés
|-
! JLE (Jump if less or equal)
| SF != OF OU ZF = 1
| si A <= BA et B sont signés
|-
! JGE (Jump if Greater)
| SF = OF OU ZF = 0
| si A > B et B sont signés
|}
==Les modes d'adressage obsolètes : données et pointeurs==
Dans cette section, nous allons voir quelques modes d'adressage autrefois utilisés sur les ordinateurs historiques, d'avant les années 90. Ils ne sont plus utilisés aujourd'hui, aucun processeur ne les supporte. Cependant, ils reviendront plus tard dans ce cours, aussi je préfère en parler maintenant. De plus, certains ont un lien avec ce qui a été dit précédemment. Nous allons tout d'abord voir un mode d'adressage pour les pointeurs.
===Les modes d'adressage indirect mémoire===
Les modes d'adressage pour les pointeurs mémorisent les pointeurs dans des registres, mais il existe quelques modes d'adressage qui mémorisent les pointeurs en mémoire RAM, à une adresse bien précise. Avec de tels modes d'adressages, le processeur accède à une adresse mémoire pour récupérer le pointeur, et l'utiliser pour un second accès. L'accès est donc indirect, par l'intermédiaire du pointeur, d'où leur nom de '''modes d'adressage indirects mémoire'''. Ils étaient utilisés autrefois sur quelques vieux ordinateurs se débrouillaient sans registres pour les données/adresses.
Du moment qu'un mode d'adressage fournit une adresse mémoire, il peut être rendu indirect. Par exemple, on peut imaginer un mode d'adressage indirect Base + indice : la somme base + indice calcule l'adresse du pointeur et non l'adresse de la donnée. Un tel mode d'adressage serait utile pour gérer des tableaux de pointeurs. Tous les modes d'adressage précédents peuvent être modifiés de manière à ce que la donnée lue/écrite soit traitée comme un pointeur. Il y a donc un grand nombre de modes d'adressages indirects mémoire !
Le plus simple d'entre eux est le '''mode d'adressage absolu indirect'''. L'instruction incorpore une adresse mémoire, comme dans l'adressage absolu. Sauf qu'il s'agit d'un adressage indirect : l'adresse n'est pas l'adresse de la donnée voulue, mais l'adresse du pointeur qui pointe vers la donnée. Un exemple est le cas des instructions LOAD et STORE des ordinateurs Data General Nova. Les deux instructions existaient en deux versions, distinguées par un bit d'indirection. Si ce bit est à 0 dans l'opcode, alors l'instruction utilise le mode d'adressage absolu normal : l'adresse intégrée dans l'instruction est celle de la donnée. Mais s'il est à 1, alors l'adresse intégrée dans l'instruction est celle du pointeur.
Il a existé des '''modes d'adressage absolus indirects avec auto-incrément/auto-décrément''', où le pointeur est incrémenté ou décrémenté automatiquement lors de l'exécution de l'instruction. Les deux exemples les plus connus sont le PDP-8 et le Data General Nova, les autres exemples sont très rares. Sur le PDP-8, les adresses 8 à 15 avaient un comportement spécial. Quand on y accédait via adressage mémoire indirect, leur contenu était automatiquement incrémenté. Le Data General Nova avait la même chose, mais pour ses adresses 16 à 31 : les adresses 16 à 24 étaient incrémentées, celles de 25 à 31 étaient décrémentées.
D'autres architectures supportaient des '''modes d'adressages indirects récursifs'''. l'idée était simple : le mode d'adressage identifie un mot mémoire, qui peut être soit une donnée soit un pointeur. Le pointeur peut lui aussi pointer vers une donnée ou un pointeur, qui lui-même... Une véritable chaine de pointeurs pouvait être supportée avec une seule instruction. Pour cela, chaque mot mémoire avait un ''bit d'indirection'' qui disait si son contenu était un pointeur ou une donnée. Des exemples d'ordinateurs supportant un tel mode d'adressage sont le DEC PDP-10, les IBM 1620, le Data General Nova, l'HP 2100 series, and le NAR 2. Le PDP-10 gérait même l'usage de registres d'indice à chaque étape d'accès à un pointeur.
===Une curiosité historique : l'instruction ''Index next'' de l'Apollo Guidance Computer===
Les tout premiers ordinateurs ne supportaient aucun mode d’adressage indirect. L'utilisation de tableaux ou de structures de données était un véritable calvaire, qui se résolvait à grand coup de code automodifiant. Les instructions d'accès mémoire incorporaient une adresse, qui était incrémentée/décrémentée par code auto-modifiant. Les branchements indirects étaient eux aussi gérés de la même manière : l'adresse de destination été incorporée dans l'instruction via adressage absolu, mais était changée via code automodifiant. Et quelques rares processeurs ont incorporé des optimisations pour simplifier l'usage du code automodifiant, voire pour s'en passer.
Un exemple est celui de l'instruction '''''Index next instruction''''', que nous appellerons INI, qui a été utilisée sur des architectures comme l'Apollo Guidance Computer et quelques autres. Elle additionne une certaine valeur à l'instruction suivante. Elle est utilisée pour émuler un adressage absolu indicé : on utilise l'INI pour ajouter l'indice à l'instruction LOAD suivante. La valeur à ajouter est précisée via mode d'adressage absolu est lue depuis la mémoire. Par exemple, si l’instruction suivante est une instruction LOAD adresse 50, l'INI permet d'y ajouter la valeur 5, ce qui donne LOAD adresse 55.
L'instruction est aussi utilisée pour modifier des branchements : si l'instruction suivante est l'instruction JUMP à adresse 100, on peut la transformer en JUMP à adresse 150. Elle peut en théorie changer l'opcode d'une instruction, ce qui permet en théorie de faire des calculs différents suivant le résultat d'une condition. Mais ces cas d'utilisation étaient assez rares, ils étaient peu fréquents.
Un point important est que l'addition a lieu à l'intérieur du processeur, pas en mémoire RAM/ROM. Le mode d'adressage ne fait pas de code auto-modifiant, l'instruction modifiée reste la même qu'avant en mémoire RAM. Elle est altérée une fois chargée par le processeur, avant son exécution.
===L'adressage relatif pour les données===
L'adressage relatif est utilisé pour les branchements, pour calculer l'adresse de destination. Mais il peut en théorie être utilisé pour les données. En clair, l'adresse d'une donnée est calculée en ajoutant au ''program counter'' un décalage, un ''offset''. Il s'agit d'une variante de l'adressage base + décalage, sauf que l'adresse de base est le ''program counter''. Il était très utilisé sur les anciens ordinateurs, qui encodaient leurs instructions sur un faible nombre de bits et ne pouvaient pas encoder d'adresses complètes.
Un exemple est celui du PDP-8, encore lui. Il avait des instructions de 12 bits, et les adresses mémoire faisaient la même taille. L'opcode était codé sur 3 bits, l'instruction incorporait une adresse codée sur 7 bits, il restait deux bits pour le mode d’adressage. Vous remarquerez que les adresses font 12 bits, mais que les instructions incorporent seulement les 7 bits de poids faible. Il faut donc trouver les 5 bits de poids fort manquants. Pour cela, trois modes d'adressage sont possibles
* avec l'adressage absolu, les 5 bits de poids fort sont mis à 0 ;
* avec l'adressage PC-relatif, les 5 bits de poids fort étaient les 5 bits de poids fort du ''program counter'' ;
* avec l'adresse indirect mémoire, l'adresse 7 bit poijnte vers un pointeur en mémoire, qui fait 12 bits.
Les deux bits du mode d'adressage permettent d'indiquer quelle option est choisie. Le premier bit indiquait si le mode d'adressage utilisé était le mode d'adressage indirect mémoire ou non. Il était à 1 pour le mode d'adressage indirect mémoire, à 0 sinon. Le second bit indiquait s'il fallait utiliser l'adressage absolu (0) ou relatif au ''program counter''.
{|class="wikitable"
|-
! Opcode !! colspan="2" | Mode d'adressage !! Adresse mémoire
|-
| class="f_rouge" | Opcode || class="f_bleu" | Bit d'indirection || class="f_bleu" | Bit d'adressage absolu/relatif || class="f_vert" | Adresse mémoire
|-
| 3 bits || 1 bit || 1 bit || 7 bits
|}
<noinclude>
{{NavChapitre | book=Fonctionnement d'un ordinateur
| prev=Le modèle mémoire : alignement et boutisme
| prevText=Le modèle mémoire : alignement et boutisme
| next=L'encodage des instructions
| nextText=L'encodage des instructions
}}
</noinclude>
lp3i9ggm58v8auw1lehpegiygsnpd6t
Wikilivres:Prise de décision/Créer un espace lecteurs et un espace auteurs sur la page d'accueil
4
84486
771990
771642
2026-09-11T17:54:59Z
Sicarov
109010
/* Discussions */
771990
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
== Décision ==
.
7p4xejjj8qcv75kxa433t0yckhjfc1f
771992
771990
2026-09-11T17:56:24Z
Sicarov
109010
/* Votes */
771992
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
== Décision ==
.
jt6ybqraoiu8bewmln70bv0q331bsbx
772004
771992
2026-09-11T19:33:45Z
Xhungab
23827
/* Votes */ Réponse
772004
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
#:Avez vous vu la deuxième proposition? [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
== Décision ==
.
6x9l4dzecnkuvzsjyxsirg5cx8507y2
772005
772004
2026-09-11T19:35:18Z
Xhungab
23827
/* Votes */
772005
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
#:Avez vous vu la deuxième proposition? [https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés] [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
== Décision ==
.
gxpv97k461r7j3lkfrgvtat2qi4ssim
772011
772005
2026-09-11T19:52:52Z
Xhungab
23827
/* Votes */
772011
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
== Décision ==
.
jt6ybqraoiu8bewmln70bv0q331bsbx
772014
772011
2026-09-11T19:55:36Z
Xhungab
23827
/* Discussions */
772014
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
== Décision ==
.
tvbfbaywp2q0ziiwdfjrgj4u7ihuskw
772032
772014
2026-09-11T20:46:51Z
Mewtow
31375
/* Votes */
772032
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en rechercdhe de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
== Décision ==
.
9kisf2t7ti0t7ur40ts8yfnbvizzplp
772033
772032
2026-09-11T20:47:26Z
Mewtow
31375
/* Votes */
772033
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
== Décision ==
.
0lzmzta6gcs24ba3gv5xx7ze3dxnuag
772036
772033
2026-09-11T21:10:52Z
Xhungab
23827
/* Votes */ Réponse
772036
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
nkbd6ziatz77oomntv83rzbqfd6tw1g
772037
772036
2026-09-11T21:11:30Z
Sicarov
109010
/* Discussions */ Réponse
772037
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
bwtf7f7x0iu382yq7xq2v7vsy3jkr8g
772038
772037
2026-09-11T21:19:36Z
Xhungab
23827
/* Discussions */ Réponse
772038
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer '''[[:Catégorie:Nouveaux livres|Nouveautés et Projets En Construction Actif]] :''' [[Chanter les psaumes]] [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
0526ggjhq32xkioonhc4xfbel7q0kvd
772041
772038
2026-09-11T21:30:29Z
Xhungab
23827
/* Discussions */
772041
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
----
----
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer '''[[:Catégorie:Nouveaux livres|Nouveautés et Projets En Construction Actif]] :''' [[Chanter les psaumes]] [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
h8z2f6s6pju0nrsnz7l5quyc3d3ueo4
772042
772041
2026-09-11T21:35:28Z
Xhungab
23827
/* Discussions */
772042
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
----
----
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer '''[[:Catégorie:Nouveaux livres|Nouveautés et Projets En Construction Actif]] :''' [[Chanter les psaumes]] Cette espace peut contenir une vingtaine de livres [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
57jgf8ce31w3ejbre734wd0oe1ye9g1
772044
772042
2026-09-11T21:40:46Z
Xhungab
23827
/* Discussions */
772044
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
----
----
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer '''[[:Catégorie:Nouveaux livres|Nouveautés et Projets En Construction Actif]] :''' [[Chanter les psaumes]] '''Cette espace peut contenir une vingtaine de livres''' [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
sd4huyy24a0wh5ctz7wk55k84xqmdij
772051
772044
2026-09-11T22:45:18Z
Xhungab
23827
/* Discussions */
772051
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
----
----
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer '''Nouveautés et Projets En Construction Actif :''' [[Chanter les psaumes]] '''Cette espace peut contenir une vingtaine de livres''' [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
q9qpk8qe6nxatle19iuq2ewt57mugfy
772052
772051
2026-09-11T22:48:27Z
Xhungab
23827
/* Discussions */
772052
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire... ne pas oublier celui qui se trouve dans l'image et les remplacer par :
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
----
----
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer
* '''Nouveautés et Projets En Construction Actif :''' [[Chanter les psaumes]]
* '''Cette espace peut contenir une vingtaine de livres'''
* '''[https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook''']'''
* [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
093uqbwyrdo8o4wmw53eh7ztejzqvb3
772075
772052
2026-09-12T10:01:26Z
Xhungab
23827
772075
wikitext
text/x-wiki
{{Prise de décision}}
==Présentation==
'''Le problème :'''
Actuellement, si un lecteur essaie de parcourir la bibliothèque à partir des listes : Arts, Loisirs, Histoire, Langues... Il se retrouve dans un labyrinte qui fini souvent sur une ébauche abandonné depuis plus de 5, 10, 15, 20 ans.
'''La solution :'''
Cette page [https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook'''] propose la vitrine, les livres disponibles, les mini-livres disponibles pour les lecteurs. Les lecteurs ne seront en contact qu'avec les livres terminés.
.
En bas à côté des nouveautés, il y a '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]''', que l'on pourrait appeler, '''le local technique'''... avec tous les livres du site rangés par thème ou par niveau d'avancement si on clique sur l'image, ou bien des listes où les livres sont rangés par catégories.
.
'''Cette proposition sépare donc deux espaces. Un pour les étudiants qui cherchent à travailler sur un des livres de la bibliothèque et un pour les personnes curieuses qui veulent connaître les coulisses de Wikibook .'''
.
Ces changements ne demandent aucune modification technique.
.
Juste la création d'une page '''[https://fr.wikibooks.org/wiki/Discussion_utilisateur:Xhungab Les coulisses de Wikibook]'''. Il suffit de copier le tableau présent dans la page vitrine actuelle, et d'ajouter un petit texte de bienvenue. (10 minutes)
.
Modification de la page vitrine actuelle.
.
Insérer le lien de la page ci-dessus devant le lien "Nouveautés". en bas de la page.
.
Supprimer tous les liens : Arts, Loisirs, Histoire.
* [[:Catégorie:Livres terminés|Livres disponibles]]
* [[:Catégorie:Minilivres|Mini Livres disponibles]]
(10 minutes)
.
En moins de 20 minutes, on a créé un espace convivial pour les lecteurs où les livres, tous terminés, sont rangés par ordre alphabétique, et un Espace technique, avec tous les livres du site rangés par thème ou par niveau d'avancement ou par catégories
.
Si cette proposition s'avérait mauvaise, il suffirait de remettre la version originale et de supprimer la page espace pour les auteurs. (2 minutes)
Merci de m'avoir suivie jusqu'ici.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:38 (CEST)
== Discussions ==
[[Wikilivres:Le Bistro/2026|Voir les discussions antécédentes dans le bisto]]
Suite à ce message :
'''Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace.'''
Dans un premier temps j'ai utilisé '''le titre un espace lecteurs et un espace auteurs'''. J'ai choisi le titre l'espace auteurs pour le mettre en opposition à l'espace lecteurs pour indiquer aux étudiants que si leur but était d'étudier leurs cours, l'espace pour les auteurs n'était pas approprié.
J'ai donc remplacé '''Un espace auteurs''' par '''Les coulisses de Wikibook''' car cela a provoqué un malentendu. Les auteurs n'ont aucune obligation d'utiliser quoi que ce soit.Cet espace donne juste accès à tout le contenu de Wikibook comme le fait la version originale de la page de Wikibook. [[Utilisateur:Xhungab|Xhungab]]
----
----
Je comprends bien l'idée de mieux distinguer dans la présentation les nouveautés finies des nouveautés en cours. Il me semble plus pertinent de distinguer cela en mettant en valeur sur une même page d'abord les livres finis tout en gardant sur la même page les livres en cours d'élaboration qui peuvent être tout aussi intéressant. Il ne me semble pas forcément pertinent d'après ce que j'ai compris, de créer un autre espace plus lointain et plus difficile d'accès pour les créations en cours. Si les coulisses de wiki livre crée un espace plus lointain et plus difficile d'accès pour ses livres en cours de création alors je suis contre la proposition. Il me semble d'ailleurs que le nombre de livres ne soit pas si grand et ne justifie pas une séparation aussi nette. Un livre en cours d'élaboration peut être tout aussi intéressant qu'un livre fini.
[[Utilisateur:Sicarov|Sicarov]]
Avez vous vu la deuxième proposition?
[https://fr.wikibooks.org/wiki/Wikilivres:Prise_de_d%C3%A9cision/Modifier_le_contenue_de_l%27espace_Nouveaut%C3%A9s Wikilivres:Prise de décision/Modifier le contenue de l'espace Nouveautés]
Les nouveaux livres et les livres en construction active seront mis en avant dans l'espace nouveauté.[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 21:33 (CEST)
:Bonjour, j'ai pu regarder l'espace des nouveautés. Je n'ai pas peut-être pas bien tout compris.
:Mais une chose dont je suis sûr c'est que plus l'information est loin avec un nombre de clics nécessaires, moins elle est accessible. J'aime beaucoup utiliser les dynamics qui sont présents sur les catégories existantes pour voir l'activité des nouveaux livres entre guillemets.
:Certaines '''Dynamic list''' bien trouvées pourrait avoir leur place '''dans la page d'accueil de wiki livre''' pour montrer un échantillon de ce qui est en train de se faire.
:J'ai également fait sur wiki source fr un switch avec plusieurs auteurs qui tournent. Un switch de livre pourrait également être proposé sur la page d'accueil avec une série de livres exemplaire et pertinent. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 23:11 (CEST)
::Les noms des nouveaux livres et des livres en constructions active apparaîtrons en dessous des trois livres de la vitrine. Il suffira de ::cliquer sur le nom du livre en construction pour tomber sur sa page. Vous pouvez essayer sur l'exemple proposer. '''Pourriez vous cliquer sur l'image des livres dans la vitrine, pour voir si cela correspond à votre proposition.'''
::* '''Nouveautés et Projets En Construction Actif :''' [[Chanter les psaumes]]
::* '''Cette espace peut contenir une vingtaine de livres'''
::* '''[https://fr.wikibooks.org/wiki/Utilisateur:Xhungab '''Wikibook''']'''
::* [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:19 (CEST)
== Votes ==
Votez {{m|Pour}} / {{m|Contre}} / {{m|Neutre}}, avec vos arguments.
# {{Pour}} Très bonne proposition. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 30 août 2026 à 00:47 (CEST)
# {{Pour}} Et je propose alors de créer deux nouvelles pages qui affichent le contenu des différentes catégories de manières esthétique et contextualisée, grâce à l'[[mw:Extension:CategoryTree]]. Une première page pour les livres terminés ([[Utilisateur:Lionel Scheepmans/page avec liste des livres terminés|exemple]]) et une seconde pour les livres inachevés.
# {{neutre}} Les autres wikis utilisent déjà les sous-pages de l'auteur pour les brouillons, donc si les nouveaux ne respectent pas ça, ils posteront encore moins dans un nouvel espace. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 30 août 2026 à 11:31 (CEST)
# {{contre}} les livres en cours de création sont tout aussi intéressants que des livres finis. Je veux éviter une plus grande invisibilisation des livres en cours de création. La proposition actuelle et peut-être plus nuancée. [[Utilisateur:Sicarov|Sicarov]] ([[Discussion utilisateur:Sicarov|discussion]]) 11 septembre 2026 à 19:56 (CEST)
# {{Pour}} Le principe est bon, clairement séparer les livres terminés de ceux en recherche de contributeurs me plait, avoir un fourre-tout accesible sur la page d'accueil n'est pas l'idéal. J'aurais juste une remarque : le nom "Les coulisses de Wikibook" n'est pas très parlant. J'aurais plus utilisé "Wikilivres en cours de rédaction" ou "Wikilivres en recherche de contributeurs".[[Utilisateur:Mewtow|Mewtow]] ([[Discussion utilisateur:Mewtow|discussion]]) 11 septembre 2026 à 22:46 (CEST)
#:Les coulisses de Wikibook était juste un nom pour la présentation de la nouvelle page. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 11 septembre 2026 à 23:10 (CEST)
== Décision ==
.
3x2lexajfnqmjcjjfzg8yeip06kdri6
Livre de cuisine/Compote d'abricots
0
84500
771987
2026-09-11T17:31:51Z
Cdang
1202
Page créée avec « {{SansGluten}} {{Livre de cuisine}} == Ingrédients == * {{unité|500|g}} d'{{i|abricot|abricots}} bien mûrs, * 50 à {{unité|100|g}} de {{i|sucre}}, * {{unité|100|ml}}/3 cuiller à soupe d'eau, * éventuellement {{unité|10|g}} de beurre, * éventuellement un peu de cannelle : 1 bâton ou une cuiller à café en poudre, * on peut si on veut ajouter un jus de citron, de la menthe… == Préparation == # Laver les abricots. # Les couper en deux et enlever les... »
771987
wikitext
text/x-wiki
{{SansGluten}}
{{Livre de cuisine}}
== Ingrédients ==
* {{unité|500|g}} d'{{i|abricot|abricots}} bien mûrs,
* 50 à {{unité|100|g}} de {{i|sucre}},
* {{unité|100|ml}}/3 cuiller à soupe d'eau,
* éventuellement {{unité|10|g}} de beurre,
* éventuellement un peu de cannelle : 1 bâton ou une cuiller à café en poudre,
* on peut si on veut ajouter un jus de citron, de la menthe…
== Préparation ==
# Laver les abricots.
# Les couper en deux et enlever les noyaux, puis les couper en petits morceaux.
# Mélanger l'eau et le sucre, éventuellement le beurre, mettre à chauffer à feu doux.
# Ajouter les abricots et la cannelle, laisser cuire 10 à {{unité|20|min}} en remuant de temps en temps.
# Éventuellement, passer au mixeur pour avoir une texture lisse ; sinon, écraser à la spatule, à la fourchette ou au presse-purée.
{{DEFAULTSORT:compote d'abricots}}
[[Catégorie:Desserts]]
[[Catégorie:Recettes végétaliennes]]
[[Catégorie:Recettes végétariennes]]
[[Catégorie:Recettes de cuisine à base de compote|abricots, compotes de]]
sfn3elgmo9dvpoh75gst91j8xcri13m
771988
771987
2026-09-11T17:32:49Z
Cdang
1202
/* Ingrédients */ typo
771988
wikitext
text/x-wiki
{{SansGluten}}
{{Livre de cuisine}}
== Ingrédients ==
* {{unité|500|g}} d'{{i|abricot|abricots|'=oui}} bien mûrs,
* 50 à {{unité|100|g}} de {{i|sucre}},
* {{unité|100|ml}}/3 cuiller à soupe d'eau,
* éventuellement {{unité|10|g}} de beurre,
* éventuellement un peu de cannelle : 1 bâton ou une cuiller à café en poudre,
* on peut si on veut ajouter un jus de citron, de la menthe…
== Préparation ==
# Laver les abricots.
# Les couper en deux et enlever les noyaux, puis les couper en petits morceaux.
# Mélanger l'eau et le sucre, éventuellement le beurre, mettre à chauffer à feu doux.
# Ajouter les abricots et la cannelle, laisser cuire 10 à {{unité|20|min}} en remuant de temps en temps.
# Éventuellement, passer au mixeur pour avoir une texture lisse ; sinon, écraser à la spatule, à la fourchette ou au presse-purée.
{{DEFAULTSORT:compote d'abricots}}
[[Catégorie:Desserts]]
[[Catégorie:Recettes végétaliennes]]
[[Catégorie:Recettes végétariennes]]
[[Catégorie:Recettes de cuisine à base de compote|abricots, compotes de]]
i7o7l3tl2tj40mpvb22bz54qu284zde
Mathc initiation/006p
0
84501
772049
2026-09-11T22:38:28Z
Xhungab
23827
news
772049
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a512#* la Transformée Inverse de Laplace : Le Théorème De Convolution| Sommaire]]
'''La transformée de Laplace Multiplication par t : ''' '''L{t F(t)} = (-1) f'(s)'''
'''Remarque :'''
L{sin(t)} = 1/(s^2+1) ... L{sin(at)} = a/(s^2+a^2)
L{cos(t)} = s/(s^2+1) ... L{cos(at)} = s/(s^2+a^2)
L{sinh(t)} = 1/(s^2-1) ... L{sinh(at)} = a/(s^2-a^2)
L{cosh(t)} = s/(s^2-1) ... L{cosh(at)} = s/(s^2-a^2)
a) Calculons la transformée de Laplace de :
L{t sin(a t)} = (-1) [a/(s^2+a^2)]'
= (-1) (a) [(s^2+a^2)^(-1)]'
= (-1) (a) (-1)(s^2+a^2)^(-2) [s^2+a^2]'
= (a) (s^2+a^2)^(-2) (2)s
2 a s
= -----------
(s^2+a^2)^2
b) Calculons la transformée de Laplace de :
s
L{t cos(a t)} = (-1) [-------]'
s^2+a^2
(1)(s^2+a^2) - s (2s)
= (-1) ---------------------
(s^2+a^2)^2
s^2+a^2 - 2s^2
= (-1) --------------
(s^2+a^2)^2
a^2-s^2
= (-1) -----------
(s^2+a^2)^2
s^2-a^2
= -----------
(s^2+a^2)^2
c) Calculons la transformée de Laplace de :
L{t sinh(a t)} = (-1) [a/(s^2-a^2)]'
= (-1) (a) [(s^2-a^2)^(-1)]'
= (-1) (a) (-1)(s^2-a^2)^(-2) [s^2-a^2]'
= (a) (s^2-a^2)^(-2) (2)s
2 a s
= ---------
(s^2-a^2)^2
d) Calculons la transformée de Laplace de :
s
L{t cosh(a t)} = (-1) [-------]'
s^2-a^2
(1)(s^2-a^2) - s(2s)
= (-1) --------------------
(s^2-a^2)^2
s^2-a^2 - 2s^2
= (-1) --------------
(s^2-a^2)^2
-s^2-a^2
= (-1) -----------
(s^2-a^2)^2
s^2+a^2
= -----------
(s^2-a^2)^2
* '''Vérifions''' avec [https://www.wolframalpha.com/input?i=laplace+transform Mathematica] :
{{AutoCat}}
823fi6uzc2gmmzti33zp5lanv2m3vb9
772050
772049
2026-09-11T22:40:13Z
Xhungab
23827
772050
wikitext
text/x-wiki
__NOTOC__
[[Catégorie:Mathc initiation (livre)]]
[[Mathc initiation/a512#* la Transformée Inverse de Laplace : Le Théorème De Convolution| Sommaire]]
'''La transformée de Laplace Multiplication par t : ''' '''L{t F(t)} = (-1) f'(s)'''
'''Remarque :'''
L{sin(t)} = 1/(s^2+1) ... L{sin(at)} = a/(s^2+a^2)
L{cos(t)} = s/(s^2+1) ... L{cos(at)} = s/(s^2+a^2)
L{sinh(t)} = 1/(s^2-1) ... L{sinh(at)} = a/(s^2-a^2)
L{cosh(t)} = s/(s^2-1) ... L{cosh(at)} = s/(s^2-a^2)
a) Calculons la transformée de Laplace de :
L{t sin(a t)} = (-1) [a/(s^2+a^2)]'
= (-1) (a) [(s^2+a^2)^(-1)]'
= (-1) (a) (-1)(s^2+a^2)^(-2) [s^2+a^2]'
= (a) (s^2+a^2)^(-2) (2)s
2 a s
= -----------
(s^2+a^2)^2
b) Calculons la transformée de Laplace de :
s
L{t cos(a t)} = (-1) [-------]'
s^2+a^2
(1)(s^2+a^2) - s (2s)
= (-1) ---------------------
(s^2+a^2)^2
s^2+a^2 - 2s^2
= (-1) --------------
(s^2+a^2)^2
a^2-s^2
= (-1) -----------
(s^2+a^2)^2
s^2-a^2
= -----------
(s^2+a^2)^2
c) Calculons la transformée de Laplace de :
L{t sinh(a t)} = (-1) [a/(s^2-a^2)]'
= (-1) (a) [(s^2-a^2)^(-1)]'
= (-1) (a) (-1)(s^2-a^2)^(-2) [s^2-a^2]'
= (a) (s^2-a^2)^(-2) (2)s
2 a s
= ---------
(s^2-a^2)^2
d) Calculons la transformée de Laplace de :
s
L{t cosh(a t)} = (-1) [-------]'
s^2-a^2
(1)(s^2-a^2) - s(2s)
= (-1) --------------------
(s^2-a^2)^2
s^2-a^2 - 2s^2
= (-1) --------------
(s^2-a^2)^2
-s^2-a^2
= (-1) -----------
(s^2-a^2)^2
s^2+a^2
= -----------
(s^2-a^2)^2
* '''Vérifions''' avec [https://www.wolframalpha.com/input?i=laplace+transform Mathematica] :
{{AutoCat}}
qpxdggrn8kmxcdq63rgpehmv042aj2h