Fonctionnement d'un ordinateur/Mémoires évoluées

Les mémoires vues au chapitre précédent sont les mémoires les plus simples qui soient. Mais ces mémoires peuvent se voir ajouter quelques améliorations pas franchement négligeables, afin d'augmenter leur rapidité, ou de diminuer leur consommation énergétique. Dans ce chapitre, nous allons voir quelles sont ces améliorations les plus courantes.

Les mémoires synchrones modifier

Les toutes premières mémoires des mémoires asynchrones, non-synchronisées avec le processeur via une horloge. Avec elles, le processeur devait attendre que la mémoire réponde et devait maintenir adresse et données pendant ce temps. Pour éviter cela, les concepteurs de mémoire ont synchronisé les échanges entre processeur et mémoire avec un signal d'horloge : les mémoires synchrones sont nées. L'utilisation d'une horloge a l'avantage d'imposer des temps d'accès fixes. Un accès mémoire prend un nombre déterminé (2, 3, 5, etc) de cycles d'horloge et le processeur peut faire ce qu'il veut dans son coin durant ce temps.

Les types de mémoires synchrones en fonction de leurs registres modifier

Fabriquer une mémoire synchrone demande de rajouter des registres sur les entrées/sorties d'une mémoire asynchrone.

La première méthode ne mémorise que l'adresse d'entrée et les signaux de commande dans un registre synchronisé sur l'horloge.

 
Mémoire synchrone première génération.

Une seconde méthode mémorise l'adresse, les signaux de commande, ainsi que les données à écrire.

 
Mémoire synchrone seconde génération.

Et la dernière méthode mémorise toutes les entrées et sorties de la mémoire dans des registres.

 
Mémoire synchrone troisième génération.

La distinction entre mémoire à flot direct et mémoire pipelinée modifier

Outre la distinction faite dans la section précédente, il faut aussi faire une distinction entre les mémoires à flot direct, les mémoires registre à verrou et les mémoires pipelinées. Les mémoires à flot direct regroupent les deux premiers cas de la section précédente.

Avec les mémoires à flot direct, soit la donnée lue ne subit pas de mémorisation dans un registre de sortie.

 
Mémoire à flot direct.

Avec les mémoires synchrones pipelinées, la donnée sortante est mémorisée dans un registre commandé par un front d'horloge, afin d'augmenter la fréquence de la mémoire et son débit. Le temps de latence des lectures est plus long avec cette technique, car il faut ajouter un cycle supplémentaire pour lire la donnée, comme on l'expliquera plus bas. Cela ralentit un peu les lectures, mais a un avantage qu'on expliquera dans ce qui suit.

 
Mémoire synchrone registre à registre.

Avec les mémoires registre à verrou, il y a un registre pour la donnée lue, mais ce registre est un registre commandé par un signal Enable et non sur un front d'horloge. Le registre commandé par un signal Enable est en quelque sorte transparent. L'usage d'un registre de ce type fait qu'on n’a pas à rajouter un cycle d'horloge pour chaque lecture, mais cela fait qu'on se prive de l'avantage des mémoires pipelinées.

 
Mémoire synchrone registre à verrou

Les mémoires synchrones non-pipelinées modifier

Grâce à l'ajout du registre d'adresse, le processeur n'a pas à maintenir l'adresse en entrée de la mémoire durant toute la durée d'un accès mémoire : le registre s'en charge. Voici ce que cela donne pour une lecture avec l'ajout d'un registre sur l'adresse uniquement. Le diagramme montre ce qu'il se passe pendant un cycle d'horloge complet. On voit que sur la mémoire asynchrone, l'adresse doit être maintenue durant toute la durée du cycle d'horloge mémoire. Mais sur la mémoire synchrone, l'adresse est envoyée en début du cycle seulement. L'avantage est que le processeur est plus rapide que la mémoire. Sur une mémoire asynchrone, le processeur devait maintenir l'adresse durant une dizaine de cycles d'horloge du processeur. Mais sur les mémoires synchrones, il maintient l'adresse durant un cycle processeur, avant de faire autre chose et de revenir quelques cycles plus tard pour lire la donnée. Le fonctionnement des écritures est similaire.

 
Différence entre mémoire asynchrone et synchrone avec mémorisation d'adresse uniquement.

L'ajout d'un registre pour les écritures permet de faire la même chose, mais pour les écritures. Sans ce registre, le processeur doit maintenir la donnée pendant toute l'écriture. Il envoie la donnée à écrire juste après l'adresse, mais doit la maintenir durant un bon bout de temps, durant lequel le processeur ne peut pas faire autre chose. L'ajout d'un registre pour la donnée écrite permet d'éliminer ce problème. La donnée à écrire est envoyée en même temps que l'adresse et le processeur n'a plus rien à faire au cycle suivant. On dit que l'on réalise une écriture anticipée.

 
Différence entre mémoire asynchrone et synchrone avec mémorisation des écritures

Un défaut des écritures anticipées est qu'elles marchent assez mal quand on enchaine les lectures et écritures. Prenons le cas simple où une lecture est suivie d'une écriture, les deux étant consécutives, séparées par un seul cycle d'horloge. Au premier cycle d'horloge, le processeur va présenter l'adresse à lire à la mémoire. Au second cycle, la donnée lue sera disponible. Mais le processeur va aussi présenter l'adresse et la donnée à écrire. Autant présenter deux adresses à la suite n'est pas un problème, autant lire une donnée et écrire la suivante en même temps en est un. Le bus de donnée ne permet pas de gérer des accès simultanés. Pour éviter cela, on peut utiliser des écritures tardives, où la donnée à écrire est présentée un cycle d'horloge après la présentation de l'adresse d'écriture.

 
Écriture tardive.

Les mémoires synchrones gèrent à la fois les écritures anticipées et les écritures tardives, sauf pour quelques exceptions. Quelques bits de commande permettent de choisir quelle écriture réaliser, pour chaque écriture. Le choix entre écriture tardives et écritures est réalisé par le processeur, et/ou par le contrôleur de mémoire externe, celui qui est placé sur la carte mère ou dans le processeur. Le choix se fait suivant les accès mémoire à réaliser, suivant qu'il y ait ou non alternance entre lectures et écritures, et bien d'autres paramètres.

Un problème avec les écritures tardives survient quand une écriture est suivie par une lecture à la même adresse. La lecture lira une donnée qui n'a pas encore été mise à jour par l'écriture. Pour éviter cela, on doit mettre en attente l'écriture dans le cas où l'adresse est la même qu'une lecture précédente. Une autre méthode revient à renvoyer directement le contenu du registre d'écriture sur le bus de donnée. Dans les deux cas, il faut ajouter un comparateur qui vérifie les deux adresses consécutives.

Les mémoires synchrones pipelinées modifier

L'ajout d'un registre pour la donnée lue a pour conséquence de décaler les lectures d'un cycle d'horloge. Sur une mémoire à flot direct (non-pipelinée), la lecture ne prend qu'un cycle d'horloge. Si l'adresse de lecture est envoyée lors d'un cycle, la donnée lue est présentée au cycle suivant. Mais avec le fonctionnement en pipeline, on rajoute un cycle d'horloge de décalage, ce qui fait que la donnée lue est disponible deux cycles après la présentation de l'adresse. La raison est que le premier cycle lit la donnée dans la RAM asynchrone et le mémorise dans le registre de sortie, mais ce n'est qu'au cycle suivant, une fois l'écriture dans le registre finie, que la donnée est disponible sur le bus.

Comme on le voit, l'accès en lecture se fait en deux étapes : on envoie l'adresse lors d'un premier cycle, effectue la lecture durant le cycle suivant, et récupère la donnée sur le bus un cycle plus tard. On pourrait aussi tenir compte du fait que la mémoire est organisée en lignes et en colonnes : l'étape de lecture peut ainsi être scindée en une étape pour sélectionner la ligne, et une autre pour sélectionner la colonne. Cela rajouterait un cycle d'horloge, vu qu'il y a une étape en plus. Mais ce découpage d'un accès mémoire en étapes a un avantage certain, qui se comprend bien en comparant une mémoire non-pipelinée et une mémoire pipelinée.

Sur une mémoire pipelinée, on doit attendre que l'accès mémoire précédent soit terminé avant d'en lancer un autre. Dans le cas d'une mémoire à flot direct, voici ce que cela donne.

 
Accès mémoires sans pipeline.

Mais en théorie, il serait possible d'envoyer l'adresse de la prochaine lecture, pendant qu'on lit la donnée de la lecture précédente. En effet, la donnée lue au cycle précédent est dans le registre de sortie et n'est donc pas altérée si on lance une lecture dans la SRAM interne.

 
Accès mémoires avec pipeline.

On peut faire la même chose, mais pour une mémoire multiplexée. Sans pipelining, on doit effectuer une sélection de la ligne, puis de la colonne, puis lire/écrire la donnée, avant de passer à l'accès suivant.

 
Accès mémoire multiplexé sans pipelining

Mais avec l'usage d'un pipeline, les choses changent. On peut accéder à une nouvelle ligne par cycle. Pendant qu'on envoie une nouvelle adresse de ligne, le tampon de ligne mémorise la ligne précédente et accède aux colonnes, pendant que le registre de sortie contient encore la donnée d'il y a deux cycles.

 
Accès mémoire multiplexé avec pipelining

Pour résumer, avec une mémoire synchrone sans pipeline, on doit attendre qu'un accès mémoire soit fini avant d'en démarrer un autre. Avec les mémoires pipelinées, on peut réaliser un accès mémoire sans attendre que les précédents soient finis. En quelque sorte, ces mémoires ont la capacité de traiter plusieurs requêtes de lecture/écriture en même temps, tant que celles-ci sont chacune à des étapes différentes. Par exemple, on peut envoyer l'adresse de la prochaine requête pendant que l'on accède à la donnée de la requête courante. Les mémoires qui utilisent ce pipelining ont donc un débit supérieur aux mémoires qui ne l'utilisent pas : le nombre d'accès mémoire traités par seconde augmente. Cette technique demande d'enchainer les différentes étapes à chaque cycle : les adresses et ordres de commande doivent arriver au bon endroit au bon moment. Pour cela, on est obligé de modifier le contrôleur de mémoire interne et y ajouter un circuit séquentiel qui envoie des ordres aux différents composants de la mémoire dans un ordre déterminé. La complexité de ce circuit séquentiel dépend fortement du nombre d'étapes utilisé pour gérer l'accès mémoire.

 
Pipemining mémoire

Précisons que le temps d'accès mémoire ne change pas beaucoup avec le pipelining. Par contre, le nombre de cycles nécessaires pour traiter une requête de lecture/écriture augmente : d'un seul cycle, on passe à autant de cycles qu'il y a d'étapes. Mais la fréquence est supérieure pour les mémoires pipelinées, ce qui compense exactement l'augmentation du nombre d'étapes. En effet, un cycle sur une mémoire non-pipelinée correspond à un accès mémoire complet, tandis qu'un cycle est égal à la durée d'une seule étape sur une mémoire pipelinée. Une explication plus détaillée sera donnée dans le chapitre sur l'usage du pipeline dans les processeurs.

Les écritures doublement tardives modifier

Plus haut, pour les mémoires non-pipelinées, nous avions vu qu'il est possible qu'il y ait des conflits d'accès où une donnée lue est envoyée sur le bus en même temps qu'une donnée à écrire. Ces conflits, qui ont lieu lors d'alternances entre lectures et écritures, sont appelés des retournements de bus.

La solution pour les mémoires non-pipelinées était de retarder la présentation de la donnée à écrire, ce qui donne des écritures tardives. Mais sur les mémoires pipelinées, les lectures sont décalées d'un cycle d'horloge, ce qui fait que cette méthode ne marche plus parfaitement. On peut se retrouver avec de tels conflits si les écritures et lectures ont des mauvais timings. Le problème est que les écritures et lectures ne prennent pas le même nombre de cycles d'horloges pour d’exécuter, ce qui fait qu'elles peuvent se marcher sur les pieds. La solution des écritures tardives, à savoir retarder la présentation de la donnée à écrire, marche toujours. Mais il faut adapter le retard pour le faire correspondre au temps des lectures. Vu que la lecture prend deux cycles sur une mémoire pipelinée, il faut retarder l'écriture de deux cycles d'horloge, et non d'un seul. Cette technique s'appelle l'écriture doublement tardive.

Les écritures doublement tardives posent le même problème que les écritures tardives. Il se peut qu'une lecture accède à une adresse écrite au cycle précédent ou dans les deux cycles précédents. La lecture lira alors une donnée pas encore mise à jour par l'écriture. Pour éviter cela, il faut soit mettre en attente la lecture, soit renvoyer le contenu du registre d'écriture sur le bus de donnée au bon timing. Dans les deux cas, il faut ajouter deux comparateurs : un qui compare l'adresse à lire avec l'adresse précédente, et un qui compare avec l'adresse d'il y a deux cycles.

L'accès en rafale modifier

L'accès en rafale est un accès mémoire qui permet de lire ou écrire un bloc de mémoire de taille fixe en envoyant une seule adresse. Concrètement, cela permet de lire/écrire plusieurs adresses mémoires consécutives les unes après les autres, en envoyant une seule adresse, en un seul accès mémoire. On envoie la première adresse et la mémoire s'occupe de lire les adresses suivantes les unes après les autres, automatiquement. L'accès en rafale fait que l'on n'a pas à envoyer plusieurs adresses pour lire un bloc de mémoire, mais une seule. Cela libère le processeur durant quelques cycles, et lui économise du travail. Un accès de ce type est appelé un accès en rafale, ou encore une rafale.

 
Accès en mode rafale.

La taille d'un bloc lu/écrit en une fois dépend de la mémoire. Il est généralement fixé une fois pour toutes et toutes les rafales ont la même taille. Par exemple, sur les mémoires asynchrones EDO-RAM, les rafales lisent/écrivent 4 octets consécutifs automatiquement, au rythme d'un par cycle d’horloge. D'autres mémoires gèrent plusieurs tailles pré-fixées, que l'on peut choisir au besoin. Par exemple, on peut choisir entre une rafale de 4 octets consécutifs, 8 octets consécutifs, ou 16 octets consécutifs. Par exemple, sur les mémoires SDRAM, on peut choisie s'il faut lire 1, 2, 4, ou 8 octets en rafale.

L'accès en rafale séquentiel, linéaire et entrelacé modifier

Il existe plusieurs types d'accès en rafale : l'accès entrelacé, l'accès linéaire et l'accès séquentiel.

Le mode séquentiel est le mode rafale normal : on accède à des octets consécutifs les uns après les autres. Peu importe l'adresse à laquelle on commence, on lit les N adresses suivantes lors de l'accès en rafale.

Le mode linéaire est un petit peu plus compliqué. Il lit un bloc de taille fixe, qui est aligné en mémoire. Par exemple, prenons un bloc de rafale de 8 octets, dont les bytes ont les adresses 0, 1, 2, 3, 4, 5, 6, et 7. Un accès linéaire n'est pas obligé de commencer par lire ou écrire le byte 0 : on peut très bien commencer par lire ou écrire au byte 3, par exemple. Dans ce cas, on commence par lire le byte numéroté 3, puis le 4, le 5, le 6 et le 7. Puis, l'accès reprend au bloc 1 et on accède aux blocs 1, 2 et 3. En clair, la mémoire est découpée en bloc de 8 bytes consécutifs et l'accès lit un bloc complet. Si la première adresse lue commence à la première adresse du bloc, l'accès est identique à l'accès séquentiel. Mais si l'adresse de départ de la rafale est dans le bloc, la lecture commence à ce bloc, puis reprend au début du bloc une fois arrivé au bout du bloc. Un accès en rafale parcourt un mot (un bloc de mots mémoires de même taille que le bus).

Le mode entrelacé utilise un ordre différent. Avec ce mode de rafale, le contrôleur mémoire effectue un XOR bit à bit entre un compteur (incrémenté à chaque accès) et l'adresse de départ pour calculer la prochaine adresse de la rafale.

Pour comprendre un petit peu mieux ces notions, nous allons prendre l'exemple du mode rafale sur les processeurs x86 présents dans nos ordinateurs actuels. Sur ces processeurs, le mode rafale permet des rafales de 4 octets, alignés sur en mémoire. Les rafales peuvent se faire en mode linéaire ou entrelacé, mais il n'y a pas de mode séquentiel. Vu que les rafales se font en 4 octets dans ces deux modes, la rafale gère les deux derniers bits de l'adresse, qui sont modifiés automatiquement par la rafale. Dans ce qui suit, nous allons indiquer les deux bits de poids faible et montrer comment ils évoluent lors d'une rafale. Le reste de l'adresse ne sera pas montré, car il pourrait être n'importe quoi.

Voici ce que cela donne en mode linéaire :

Accès en mode rafale de type linéaire sur les processeurs x86.
1er accès 2nd accès 3ème accès 4ème accès
Exemple 1 00 01 10 11
Exemple 2 01 10 11 00
Exemple 3 10 11 00 01
Exemple 4 11 00 01 10

Voici ce que cela donne en mode entrelacé :

Accès en mode rafale de type entrelacé sur les processeurs x86.
1er accès 2nd accès 3ème accès 4ème accès
Exemple 1 00 01 10 11
Exemple 2 01 00 11 10
Exemple 3 10 11 00 01
Exemple 4 11 10 01 00

L'implémentation des accès en rafale modifier

Au niveau de la microarchitecture, l'accès en rafale s'implémente en ajoutant un compteur dans la mémoire. L'adresse de départ est mémorisée dans un registre en aval de la mémoire. Ce registre n'est autre que le registre qui permet de transformer une mémoire asynchrone en mémoire synchrone, qu'on a vu plus haut.

Pour gérer les accès en rafale séquentiels, il suffit que le registre qui stocke l'adresse mémoire à lire/écrire soit transformé en compteur.

Pour les accès en rafale linéaire, le compteur est séparé de ce registre. Ce compteur est initialisé à 0 lors de la transmission d'une adresse, mais est incrémenté à chaque cycle sinon. L'adresse à lire/écrire à chaque cycle se calcule en additionnant l'adresse de départ, mémorisée dans le registre, au contenu du compteur. Pour les accès en rafale entrelacés, c'est la même chose, sauf que l'opération effectuée entre l'adresse de départ et le compteur n'est pas une addition, mais une opération XOR bit à bit.

 
Microarchitecture d'une RAM avec accès en rafale linéaire.

Le préchargement et les mémoires de type Dual et quad data rate modifier

Les processeurs sont de plus en plus exigeants et la vitesse de la mémoire commence à être de plus en plus limitante pour leurs performances. Pour augmenter la vitesse de la mémoire, la solution la plus évidente est d'augmenter sa fréquence. Mais le seul problème, c'est que c'est plus facile à dire qu'à faire ! Il faut dire que le plan mémoire ne peut pas vraiment être rendu plus rapide, compte tenu des contraintes techniques actuelles. Une autre solution pourrait être d'augmenter le débit de la mémoire, ce qui revient à échanger plus de données par cycle d'horloge. Cette solution à l'avantage de profiter de la localité spatiale, le fait que les programmes ordinaires ont souvent besoin d’accéder à des données consécutives. Problème : il faudrait rajouter des broches sur la mémoire et câbler plus de fils. Le prix de la mémoire s'envolerait et elle serait bien plus difficile à concevoir, sans compter les difficultés pour faire fonctionner l'ensemble à haute fréquence.

Si accroître sa fréquence ou la largeur du bus a trop de désavantages, il existe une solution alternative, qui est une sorte de mélange des deux techniques. Cette technique s'appelle le préchargement, prefetching en anglais. Elle donne naissance aux mémoires mémoires Dual Data Rate, aussi appelées mémoires DDR. Il s'agit de mémoires SDRAM améliorées, avec une interface avec la mémoire légèrement bidouillée.

Les mémoires sans préchargement modifier

Les mémoires sans préchargement sont appelées des mémoires SDR (Single Data Rate). Avec elles, le plan mémoire et le bus vont à la même fréquence et ils ont la même largeur (le nombre de bits transmit en une fois). Par exemple, si le bus mémoire a une largeur de 64 bits et une fréquence de 100 MHz, alors le plan mémoire fait de même. Toute augmentation de la fréquence et/ou de la largeur du bus se répercute sur le plan mémoire et réciproquement. Problème, le plan mémoire est difficile à faire fonctionner à haute fréquence, mais peut avoir une largeur assez importante sans problèmes. Pour le bus, c'est l'inverse : le faire fonctionner à haute fréquence est possible, bien que cela requière un travail d'ingénierie assez conséquent, alors qu'en augmenter la largeur poserait de sérieux problèmes.

 
Mémoire SDR.

Les mémoires avec préchargement modifier

L'idée du préchargement est un compromis idéal entre les deux contraintes précédentes : on augmente la largeur du plan mémoire sans en augmenter la fréquence, mais on fait l'inverse pour le bus. En faisant cela, le plan mémoire a une fréquence inférieure à celle du bus, mais a une largeur plus importante : on peut y lire ou y écrire 2, 4, 8 fois plus de données d'un seul coup. Le contrôleur et le bus mémoire fonctionnent à une fréquence plus élevée de celle du plan mémoire, pour compenser. Si le plan mémoire a une largeur de N fois celle du bus, le bus a une fréquence N plus élevée pour compenser.

Sur les mémoires DDR (Double Data Rate), le plan mémoire est deux fois plus large que le bus, mais a une fréquence deux fois plus faible. Les données lues ou écrites dans le plan mémoire sont envoyées en deux fois sur le bus, ce qui est compensé par le fait qu'il soit deux fois plus rapide. Ceci dit, il faut trouver un moyen pour découper un mot mémoire de 128 bits en deux blocs de 64, à envoyer sur le bus dans le bon ordre. Cela se fait dans l'interface avec le bus, grâce à une sorte de mémoire tampon un peu spéciale, dans laquelle on accumule les 128 bits lus ou à écrire.

 
Mémoire DDR.
Sur les mémoires DDR dans les ordinateurs personnels, seul un signal d'horloge est utilisé, que ce soit pour le bus, le plan mémoire, ou le contrôleur. Seulement, le bus et les contrôleurs mémoire réagissent à la fois sur les fronts montants et sur les fronts descendants de l'horloge. Le plan mémoire, lui, ne réagit qu'aux fronts montants.

Il existe aussi des mémoires quad data rate, pour lesquelles la fréquence du bus est quatre fois celle du plan mémoire. Évidemment, la mémoire peut alors lire ou écrire 4 fois plus de données par cycle que ce que le bus peut supporter.

 
Mémoire QDR.

Vous remarquerez que le préchargement se marie extrêmement bien avec le mode rafale.

Le préchargement augmente donc le débit théorique maximal. Sur les mémoires sans préchargement, le débit théorique maximal se calcule en multipliant la largeur du bus de données par sa fréquence. Par exemple, une mémoire SDRAM fonctionnant à 133 Mhz et qui utilise un bus de 8 octets, aura un débit de 8 * 133 * 1024 * 1024 octets par seconde, ce qui fait environ du 1 giga-octets par secondes. Pour les mémoires DDR, il faut multiplier la largeur du bus mémoire par la fréquence, et multiplier le tout par deux pour obtenir le débit maximal théorique. En reprenant notre exemple d'une mémoire DDR fonctionnant à 200 Mhz et utilisée en simple channel utilisera un bus de 8 octets, ce qui donnera un débit de 8 * 200 * 1024 * 1024 octets par seconde, ce qui fait environ du 2.1 gigaoctets par secondes.

Les banques et rangées modifier

Sur certaines puces mémoires, un seul boitier peut contenir plusieurs mémoires indépendantes regroupées pour former une mémoire unique plus grosse. Chaque sous-mémoire indépendante est appelée une banque, ou encore un banc mémoire. La mémoire obtenue par combinaison de plusieurs banques est appelée une mémoire multi-banques. Cette technique peut servir à améliorer les performances, la consommation d'énergie, et j'en passe. Par exemple, cela permet de faciliter le rafraichissement d'une mémoire DRAM : on peut rafraichir chaque sous-mémoire en parallèle, indépendamment des autres. Mais cette technique est principalement utilisée pour doubler le nombre d'adresses, doubler la taille d'un mot mémoire, ou faire les deux.

 
Mémoire multi-banques.

L'arrangement horizontal modifier

L'arrangement horizontal utilise plusieurs banques pour augmenter la taille d'un mot mémoire sans changer le nombre d'adresses. Chaque banc mémoire contient une partie du mot mémoire final. Avec cette organisation, on accède à tous les bancs en parallèle à chaque accès, avec la même adresse.

 
Arrangement horizontal.

Pour l'exemple, les barrettes de mémoires SDRAM ou DDR-RAM des PC actuels possèdent un mot mémoire de 64 bits, mais sont en réalité composées de 8 sous-mémoires ayant un mot mémoire de 8 bits. Cela permet de répartir la production de chaleur sur la barrette : la production de chaleur est répartie entre plusieurs puces, au lieu d'être concentrée dans la puce en cours d'accès. La technologie dual-channel est basée sur le même principe. Sauf qu'au lieu de rassembler plusieurs puces mémoires sur une même barrette, on fait la même chose avec plusieurs barrettes de mémoires. Ainsi, on peut connecter deux barrettes avec un mot mémoire de 64 bits et on les relie à un bus de 128 bits.

L'arrangement vertical modifier

L'arrangement vertical rassemble plusieurs boitiers de mémoires pour augmenter la capacité sans changer la taille d'un mot mémoire. On utilisera un boitier pour une partie de la mémoire, un autre boitier pour une autre, et ainsi de suite. Toutes les banques sont reliées au bus de données, qui a la même largeur que les sorties des banques. Une partie de l'adresse est utilisée pour choisir à quelle banque envoyer les bits restants de l'adresse. Les autres banques sont désactivées. Mais un arrangement vertical peut se mettre en œuvre de plusieurs manières différentes.

La première méthode consiste à connecter la bonne banque et déconnecter toutes les autres. Pour cela, on utilise la broche CS, qui connecte ou déconnecte la mémoire du bus. Cette broche est commandée par un décodeur, qui prend les bits de poids forts de l'adresse en entrée.

 
Comparaison entre arrangement horizontal (à gauche) et arrangement vertical (à droite).

Une autre solution est d'ajouter un multiplexeur/démultiplexeur en sortie des banques et de commander celui-ci convenablement avec les bits de poids forts. Le multiplexeur sert pur les lectures, le démultiplexeur pour les écritures.

 
Circuits d'une mémoire interleaved par rafale.

Les mémoires non-entrelacées modifier

Sans la technique dite de l'entrelacement, qu'on verra dans la section suivante, on utilise les bits de poids forts pour sélectionner la banque, ce qui fait que les adresses sont réparties comme illustré dans le schéma ci-dessous. Un défaut de cette organisation est que, si on souhaite lire/écrire deux mots mémoires consécutifs, on devra attendre que l'accès au premier mot soit fini avant de pouvoir accéder au suivant (vu que ceux-ci sont dans la même banque).

 
Répartition des adresses sans entrelacement.

L'entrelacement basique modifier

Avec l'organisation précédente, les accès séquentiels se font dans la même banque, ce qui les rend assez lents. Mais il est possible d'accélérer les accès à des bytes consécutifs en rusant quelque peu. L'idée est que des accès consécutifs se fassent dans des banques différentes, et donc que des bytes consécutifs soient localisés dans des banques différentes. Les mémoires qui fonctionnent sur ce principe sont appelées des mémoires à entrelacement simple.

 
Répartition des adresses dans une mémoire interleaved.

Pour cela, il suffit de prendre une mémoire à arrangement vertical, avec un petit changement : il faut utiliser les bits de poids faible pour sélectionner la banque, et les bits de poids fort pour le Byte.

 
Adresse mémoire d'une mémoire entrelacée

En faisant cela, on peut accéder à un plusieurs bytes consécutifs assez rapidement. Cela rend les accès en rafale plus rapide. Pour cela, deux méthodes sont possibles.

  • La première méthode utilise un accès en parallèle aux banques, d'où son nom d'accès entrelacé parallèle. Sans entrelacement, on doit accéder à chaque banque l'une après l'autre, en lisant chaque byte l'un après l'autre. Avec l’entrelacement parallèle, on lit plusieurs bytes consécutifs en même temps, en accédant à toutes les banques en même temps, avant d'envoyer chaque byte l'un après l'autre sur le bus (ce qui demande juste de configurer le multiplexeur). Un tel accès est dit en rafale : on envoie une adresse, puis on récupère plusieurs bytes consécutifs à partir de cette adresse initiale.
  • Une autre méthode démarre un nouvel accès mémoire à chaque cycle d'horloge, pour lire des bytes consécutifs un par un, mais chaque accès se fera dans une banque différente. En faisant cela, on n’a pas à attendre que la première banque ait fini sa lecture/écriture avant de démarrer la lecture/écriture suivante. Il s'agit d'une forme de pipelining, qui fait que l'accès à des bytes consécutifs est rendu plus rapide.

Les mémoires à entrelacement par décalage modifier

Les mémoires à entrelacement simple ont un petit problème : sur une mémoire à N banques, des accès dont les adresses sont séparées par N mots mémoires vont tous tomber dans la même banque et seront donc impossibles à pipeliner. Pour résoudre ce problème, il faut répartir les mots mémoires dans la mémoire autrement. Dans les explications qui vont suivre, la variable N représente le nombre de banques, qui sont numérotées de 0 à N-1.

Pour obtenir cette organisation, on va découper notre mémoire en blocs de N adresses. On commence par organiser les N premières adresses comme une mémoire entrelacée simple : l'adresse 0 correspond à la banque 0, l'adresse 1 à la banque 1, etc. Pour le bloc suivant, nous allons décaler d'une adresse, et continuer à remplir le bloc comme avant. Une fois la fin du bloc atteinte, on finit de remplir le bloc en repartant du début du bloc. Et on poursuit l’assignation des adresses en décalant d'un cran en plus à chaque bloc. Ainsi, chaque bloc verra ses adresses décalées d'un cran en plus comparé au bloc précédent. Si jamais le décalage dépasse la fin d'un bloc, alors on reprend au début.

 
Mémoire entrelacée par décalage.

En faisant cela, on remarque que les banques situées à N adresses d'intervalle sont différentes. Dans l'exemple du dessus, nous avons ajouté un décalage de 1 à chaque nouveau bloc à remplir. Mais on aurait tout aussi bien pu prendre un décalage de 2, 3, etc. Dans tous les cas, on obtient un entrelacement par décalage. Ce décalage est appelé le pas d'entrelacement, noté P. Le calcul de l'adresse à envoyer à la banque, ainsi que la banque à sélectionner se fait en utilisant les formules suivantes :

  • adresse à envoyer à la banque = adresse totale / N ;
  • numéro de la banque = (adresse + décalage) modulo N, avec décalage = (adresse totale * P) mod N.

Avec cet entrelacement par décalage, on peut prouver que la bande passante maximale est atteinte si le nombre de banques est un nombre premier. Seulement, utiliser un nombre de banques premier peut créer des trous dans la mémoire, des mots mémoires inadressables. Pour éviter cela, il faut faire en sorte que N et la taille d'une banque soient premiers entre eux : ils ne doivent pas avoir de diviseur commun. Dans ce cas, les formules se simplifient :

  • adresse à envoyer à la banque = adresse totale / taille de la banque ;
  • numéro de la banque = adresse modulo N.

L'entrelacement pseudo-aléatoire modifier

Une dernière méthode de répartition consiste à répartir les adresses dans les banques de manière pseudo-aléatoire. La première solution consiste à permuter des bits entre ces champs : des bits qui étaient dans le champ de sélection de ligne vont être placés dans le champ pour la colonne, et vice-versa. Pour ce faire, on peut utiliser des permutations : il suffit d'échanger des bits de place avant de couper l'adresse en deux morceaux : un pour la sélection de la banque, et un autre pour la sélection de l'adresse dans la banque. Cette permutation est fixe, et ne change pas suivant l'adresse. D'autres inversent les bits dans les champs : le dernier bit devient le premier, l'avant-dernier devient le second, etc. Autre solution : couper l'adresse en morceaux, faire un XOR bit à bit entre certains morceaux, et les remplacer par le résultat du XOR bit à bit. Il existe aussi d'autres techniques qui donnent le numéro de banque à partir d'un polynôme modulo N, appliqué sur l'adresse.

Les rangées modifier

Si on mélange l'arrangement vertical et l'arrangement horizontal, on obtient ce que l'on appelle une rangée. Sur ces mémoires, les adresses sont découpées en trois morceaux, un pour sélectionner la rangée, un autre la banque, puis la ligne et la colonne.

Les mémoires multiports modifier

Les mémoires multiports sont reliées non pas à un, mais à plusieurs bus. Chaque bus est connecté sur la mémoire sur ce qu'on appelle un port. Ces mémoires permettent de transférer plusieurs données à la fois, une par port. Le débit est sont donc supérieur à celui des mémoires mono-port. De plus, chaque port peut être relié à des composants différents, ce qui permet de partager une mémoire entre plusieurs composants. Comme autre exemple, certaines mémoires multiports ont un bus sur lequel on ne peut que lire une donnée, et un autre sur lequel on ne peut qu'écrire.

 
Mémoire multiport.

Le multiports idéal modifier

Une première solution consiste à créer une mémoire qui soit vraiment multiports. Avec une mémoire multiports, tout est dupliqué sauf les cellules mémoire. Pour rappel, dans une mémoire normale, chaque cellule mémoire est relié à bitline via un transistor, lui-même commandé par le décodeur. Chaque port a sa propre bitline dédiée, ce qui donne N bitlines pour une mémoire à N ports. Évidemment, cela demande d'ajouter des transistors de sélection, pour la connexion et la déconnexion. De plus, ces transistors sont dorénavant commandés par des décodeurs différents : un par port. Et on a autant de duplications que l'on a de ports : N ports signifie tout multiplier par N. Autant dire que ce n'est pas l'idéal en termes de consommation énergétique !

Cette solution pose toutefois un problème : que se passe-t-il lorsque des ports différents écrivent simultanément dans la même cellule mémoire ? Eh bien tout dépend de la mémoire : certaines donnent des résultats plus ou moins aléatoires et ne sont pas conçues pour gérer de tels accès, d'autres mettent en attente un des ports lors de l'accès en écriture. Sur ces dernières, il faut évidemment rajouter des circuits pour détecter les accès concurrents et éviter que deux ports se marchent sur les pieds.

Le multiports à état partagé modifier

Certaines mémoires ont besoin d'avoir un très grand nombre de ports de lecture. Pour cela, on peut utiliser une mémoire multiports à état dupliqué. Au lieu d'utiliser une seule mémoire de 20 ports de lecture, le mieux est d'utiliser 4 mémoires qui ont chacune 5 ports de lecture. Toutefois, ces quatre mémoires possèdent exactement le même contenu, chacune d'entre elles étant une copie des autres : toute donnée écrite dans une des mémoires l'est aussi dans les autres. Comme cela, on est certain qu'une donnée écrite lors d'un cycle pourra être lue au cycle suivant, quel que soit le port, et quelles que soient les conditions.

 
Mémoire multiport à état partagé.

Le multiports externe modifier

D'autres mémoires multiports sont fabriquées à partir d'une mémoire à un seul port, couplée à des circuits pour faire l'interface avec chaque port.

 
Mémoire multiport à multiportage externe.

Une première méthode pour concevoir ainsi une mémoire multiports est d'augmenter la fréquence de la mémoire mono-port sans toucher à celle du bus. À chaque cycle d'horloge interne, un port a accès au plan mémoire.

La seconde méthode est basée sur des stream buffers. Elle fonctionne bien avec des accès à des adresses consécutives. Dans ces conditions, on peut tricher en lisant ou en écrivant plusieurs blocs à la fois dans la mémoire interne mono-port : la mémoire interne a un port très large, capable de lire ou d'écrire une grande quantité de données d'un seul coup. Mais ces données ne pourront pas être envoyées sur les ports de lecture ou reçues via les ports d'écritures, nettement moins larges. Pour la lecture, il faut obligatoirement utiliser un circuit qui découpe les mots mémoires lus depuis la mémoire interne en données de la taille des ports de lecture, et qui envoie ces données une par une. Et c'est la même chose pour les ports d'écriture, si ce n'est que les données doivent être fusionnées pour obtenir un mot mémoire complet de la RAM interne.

Pour cela, chaque port se voit attribuer une mémoire qui met en attente les données lues ou à écrire dans la mémoire interne : le stream buffer. Si le transfert de données entre RAM interne et stream buffer ne prend qu'un seul cycle, ce n'est pas le cas pour les échanges entre ports de lecture et écriture et stream buffer : si le mot mémoire de la RAM interne est n fois plus gros que la largeur d'un port de lecture/écriture, il faudra envoyer le mot mémoire en n fois, ce qui donne n^cycles. Ainsi, pendant qu'un port accèdera à la mémoire interne, les autres ports seront occupés à lire le contenu de leurs stream buffers. Ces stream buffers sont gérés par des circuits annexes, pour éviter que deux stream buffers accèdent en même temps dans la mémoire interne.

 
Mémoire multiport streamée.

La troisième méthode remplace les stream buffers par des caches, et utilise une mémoire interne qui ne permet pas de lire ou d'écrire plusieurs mots mémoires d'un coup. Ainsi, un port pourra lire le contenu de la mémoire interne pendant que les autres ports seront occupés à lire ou écrire dans leurs caches.

 
Mémoire à multiports caché.

La méthode précédente peut être améliorée, en utilisant non pas une seule mémoire monoport en interne, mais plusieurs banques monoports. Dans ce cas, il n'y a pas besoin d'utiliser de mémoires caches ou de stream buffers : chaque port peut accéder à une banque tant que les autres ports n'y touchent pas. Évidemment, si deux ports veulent lire ou écrire dans la même banque, un choix devra être fait et un des deux ports devra être mis en attente.

 
Mémoire à multiports par banques.