# Fonctionnement d'un ordinateur/L'adressage des périphériques

Dans le chapitre précédent, nous avons vu que les périphériques, leurs registres d’interface et leurs contrôleurs, ont chacun une adresse bien précise. Nous avions vu comment le contrôleur de périphérique adresse les périphériques et comment les contrôleurs de périphériques eux-mêmes ont des adresses. Mais nous n'avons pas vu comment le processeur utilise ces adresses.

## Rappels : l'espace d'adressage unifié ou séparé

Comment s'opère le mélange entre adresses mémoires et adresses de périphérique ? Comment le processeur évite les confusions entre adresses de périphériques et adresses mémoire. Pour cela, il y a plusieurs manières. La plus simple revient à séparer les adresses mémoire et les adresses périphériques, qui ne sont pas transmises sur les mêmes bus. L'autre méthode revient à utiliser un seul ensemble d'adresse, certaines étant allouées à la mémoire, d'autres aux périphériques. Les deux techniques portent des noms assez clairs : l'espace d'adressage séparé pour la première, l'espace d'adressage unifié pour la seconde. Voyons dans le détail ces deux techniques.

### L'espace d’adressage séparé

Avec la première technique, mémoire et entrées-sorties sont adressées séparément, comme illustré dans le schéma ci-dessous. La mémoire et les entrées-sorties ont chacune un ensemble d'adresse, qui commence à 0 et va jusqu’à une adresse maximale. On dit que la mémoire et les entrées-sorties ont chacune leur propre espace d'adressage.
Avec cette technique, le processeur doit avoir des instructions séparées pour gérer les périphériques et adresser la mémoire. Il a des instructions de lecture/écriture pour lire/écrire en mémoire, et d'autres pour lire/écrire les registres d’interfaçage. L'existence de ces instructions séparées permet de faire la différence entre mémoire et périphérique. Sans cela, le processeur ne saurait pas si une adresse est destinée à un périphérique ou à la mémoire.

### Les entrées-sorties mappées en mémoire

La seconde technique s'appelle l'espace d'adressage unifie, ou encore les entrées-sorties mappées en mémoire. Avec cette technique, certaines adresses mémoires sont redirigées automatiquement vers les périphériques. Le périphérique se retrouve inclus dans l'ensemble des adresses utilisées pour manipuler la mémoire : on dit qu'il est mappé en mémoire.
L'avantage de cette méthode est la simplicité pour les programmeurs. Il n'y a pas besoin d'instructions différentes pour accéder aux périphériques et à la mémoire. Tout peut être fait par une seule instruction, qui n'a pas besoin de positionner un quelconque bit IO qui n'existe plus. Le processeur possède donc un nombre plus limité d'instructions machines, et est donc plus simple à fabriquer. Mais surtout, les programmeurs peuvent accéder aux périphériques beaucoup plus simplement, en lisant ou écrivant directement dans certaines adresses associées aux périphériques. Les transferts entre mémoire et périphériques sont fortement simplifiés, par exemple.

## Les bus unifiés et les bus d'entrée-sortie

Maintenant que nous venons de rappeler ce que sont les espaces d'adressage unifiés et séparés, il est temps de voir comment ils sont implémentés en matériel. Et pour cela, nous allons encore une fois faire un rappel sur les bus.
Nous avons vu dans les chapitres précédents qu'il existe en gros trois configurations de base pour les bus.
La première est celle du bus système, un bus unique qui relie la mémoire RAM, la mémoire ROM, le processeur, et les entrées-sorties.
La seconde est celle d'un bus séparé pour les entrées-sorties, appelé le bus d'entrée-sortie. Il est en théorie possible d'avoir un bus séparé par controleur de périphérique ou entrée-sortie, mais le nombre de broches utilisé devient rapidement important, ce qui fait que cette technique n'est jamais implémentée.
La troisième intercale un circuit répartiteur entre le processeur et les deux bus. Il s'occupe alors de la gestion des adresses.

### L'implémentation d'un bus IO dédié avec un multiplexeur d'IO

Les anciens ordinateurs des années 80-90 utilisaient un mélange des techniques 2 et 3 présentées juste au-dessus. Les processeurs avaient un bus d'entrée-sortie séparé des autres, et notamment séparé du bus mémoire. Mais ce bus était connecté à un répartiteur spécialisé dans les IO, qui s'occupait uniquement des entrées-sorties. Les répartiteurs étaient nombreux à l'époque et étaient appelés des circuits de parallel IO, bien que ce terme signifie autre chose de nos jours. Les plus connus sont le 8255 d'Intel, le Motorola 6820 PIA (Peripheral Interface Adapter), le WDC 65C21, le MOS Technology 6522 et le MOS Technology CIA.
Pour simplifier les explications, le circuit répartiteur sera appelé un multiplexeur d'entrées-sorties ou encore un IO MUX. En effet, c'est fondamentalement un multiplexeur/démultiplexeur amélioré. Pour simplifier, un IO MUX dispose de plusieurs ports d'entrée-sortie, un pour le processeur et les autres pour les contrôleurs de périphérique. La liaison point à point entre le CPU et l'IOMUX se faisait sur des broches dédiées, regroupées dans le port CPU, ou port processeur. L'IO MUX avait plusieurs ports IO, ou ports d'entrées-sorties, sur lesquels on connectait un contrôleur de périphérique via une liaison point à point.
Un exemple est celui du 8255, qui disposait de trois ports IO et d'un port CPU. Le port CPU est un port de 8 bits, qui correspond aux broches D0 à D7. Les ports IO sont des ports de 8 bits et sont appelés les ports A, B et C. Leurs broches sont respectivement les broches PA0 à PA7 pour le port A, les broches PB0 à PB7 pour le port B, les broches PC0 à PC7 pour le port C.
    Le 8255 était plus complexe que ce qui est décrit. Le port C servait soit de port IO proprement dit, soit regroupait les bits de contrôle des ports A et B, à savoir les bits de contrôle pour les interruptions et le handshaking. Le 8255 avait aussi plusieurs modes de fonctionnement où les ports IO étaient configurés différemment, le choix du mode étant fait en configurant un registre de contrôle interne au 8255. Le registre de contrôle était adressé via les lignes 10 et A1 qu'on verra plus bas.
Les ports CPU et IO pouvaient fonctionner comme entrée ou sortie et changeaient de rôle suivant la situation, suivant que le CPU pouvait émettre des données en direction d'un périphérique, ou en recevoir. L'IO MUX fonctionnait soit comme un multiplexeur, soit comme un démultiplexeur. Lorsque le processeur envoyait une donnée vers un périphérique, il fonctionnait en démultiplexeur, pour envoyer la donnée vers le bon périphérique, le bon port. En réception, il fonctionnait en multiplexeur et choisissait quel port était connecté au port CPU, quel port envoyait ses données vers le CPU.
Le choix entre multiplexage et démultiplexage se faisait selon que le processeur voulait faire une lecture ou une écriture. Le choix entre les deux était donc le fait d'une entrée de l'IO MUX, l'entrée R/W, qui indiquait s'il fallait faire une lecture ou une écriture.
Qui dit multiplexage/démultiplexage dit : choisir le port IO à connecter au port CPU. Pour cela, les ports étaient numérotés et le CPU pouvait préciser le numéro du port voulu. Et le numéro du port voulu était présenté sur une entrée dédiée, comme sur un MUX ou DEMUX normal. En soit, ce numéro est équivalent à une adresse de périphérique/port, ce qui fait que cette entrée était en réalité un bus d'adresse, appartenant au port CPU. Sur le 8255, l'envoi de l'adresse se faisait sur les deux broches A0 et A1, qui codaient un numéro de 2 bits. Les valeurs étaient les suivantes : 00 = port A, 01 = port B, 10 = port C, 11 = registre de contrôle.
Mais l'IO MUX n'est pas qu'un simple MUX/DEMUX configurable. Il pouvait générer des signaux d'interruption. Quand un périphérique envoyait une donnée à l'IO MUX, il générait un signal d'interruption pour prévenir le processeur qu'une IO a envoyé une donnée. De plus, le répartiteur pouvait mettre en attente les données dans des registres, qui servaient de registres d’interfaçage. Par exemple, une donnée lue sur un port IO était mémorisée dans le répartiteur en attendant que le processeur la récupère. Et inversement, le processeur pouvait envoyer une donnée à un périphérique par l'intermédiaire d'un registre dans le répartiteur. Il écrivait dans ce registre, la donnée était mise en attente dedans en attendant que le périphérique soit libre, et le répartiteur envoyait la donnée quand ce dernier était libéré.
Il faut noter que les ports IO peuvent être aussi bien série que parallèle. Le 8255 avait trois ports IO de 8 bits, qui sont donc tous les trois des ports parallèles. Mais il a existé des IO MUX disposant de deux ports parallèles et un port série. Tel est le cas du MOS Technology 6522 et de son successeur, le MOS Technology CIA. C'était des IO MUX utilisés dans les ordinateurs Commodore, l'Apple III, et quelques autres ordinateurs anciens renommés.
Ils disposaient de deux ports parallèles de 8 bits (PA0-7, PB0-7), chacun ayant 4 lignes de contrôles à leur disposition pour les interruptions, et d'un port série (CB1 et CB2). Le port série était connecté à un registre à décalage de 8 bits, ce qui lui permettait d'envoyer/recevoir un octet à la fois. Ils intégraient aussi des timers de 16 bits, ainsi qu'une Real Time Clock pour gérer l'heure.

### Le contrôleur DMA sur un bus partagé ou avec un répartiteur

L'usage d'un bus dédié a des avantages et des désavantages. L'un des désavantages est lié à l'implémentation du Direct Memory Access. Un bus dédié aux IO marche assez mal avec le DMA. Les échanges entre mémoire RAM et IO doivent passer par le processeur, vu que le bus mémoire est séparé du bus des IO et que le seul point de contact entre les deux est le CPU.
A l'opposé, un contrôleur DMA est très adapté à un système avec un bus système, un bus partagé entre CPU, RAM et IO. Le controleur DMA est alors connecté au bus et il se réserve l'accès au bus quand il effectue un transfert DMA.
L'usage d'un répartiteur ne pose pas de problèmes particuliers pour implémenter le DMA. La seule contrainte est que le contrôleur DMA soit intégré dans le répartiteur. Les échanges entre IO et mémoire passent par le répartiteur, qui fait le pont entre bus mémoire et bus des IO.

### Les coprocesseurs d'entrée-sortie sur les bus partagés

Les bus partagés étaient parfois couplés avec des contrôleurs DMA améliorés, qui étaient rendus programmables. De tels contrôleurs DMA améliorés étaient appelés avec des termes très divers : Channel I/O, I/O processor, I/O controller, I/O synchronizer, et autres. Dans ce qui va suivre, nous allons utiliser le terme de coprocesseur périphérique.
De tels coprocesseurs périphériques permettent de décharger le processeur principal, le CPU (Central Processing Unit), de tout ce qui a trait aux entrées-sorties. Il est moins performant en calcul que le processeur principal, l'accent étant mis sur les accès mémoire. Sauf erreur, ils ne sont présents que sur les systèmes avec un bus partagé, il n'en existe pas pour les systèmes avec un répartiteur ou uns IO dédié.
Les coprocesseurs périphériques exécutent un programme qui agit sur les entrées-sorties, souvent appelé un programme périphérique. Il y a généralement un programme périphérique pour chaque périphérique. Par exemple, il y a un programme pour le contrôleur de disquette, un autre pour le contrôleur du lecteur de bande magnétique, un autre pour la carte graphique, etc. Les programmes périphériques sont fournis par le système d'exploitation et on peut les voir comme des pilotes de périphériques simplifiés. Ils sont équivalents à ce que donnerait des routines d'interruptions déportées sur un autre processeur .
Avec un coprocesseur périphérique, le CPU ne dispose que d'une seule instruction liées aux entrées-sorties. Elle initie l'exécution d'un programme d'entrée-sortie sur le coprocesseur périphérique, en lui fournissant un pointeur qui pointe vers le programme périphérique (l'adresse du programme). Suite à cette instruction, le coprocesseur périphérique exécute son programme dans son coin, séparément du processeur. Quand le programme périphérique s'est terminé, le coprocesseur périphérique envoie une interruption au CPU pour le prévenir qu'il a terminé. Idem en cas d'erreur lors de la transmission.
Dans le cas général, le programme émule les transferts de données entre RAM et périphériques, qui sont autrement gérés par des transferts DMA. Pour cela, les coprocesseurs périphériques peuvent émuler le DMA avec un programme spécialisé qui copie les mots mémoire un par un avec une boucle. Le coprocesseur périphérique incorpore pour cela plusieurs registres spécialisés : des registres contenant des pointeurs, des registres pour des indices de boucle ou des compteurs. Les registres pointeurs sont généralement incrémentés ou décrémentés, suivant que le transfert se fasse par adresses croissantes ou décroissantes. Les compteurs de boucles sont décrémentés à chaque itération et mémorisent à tout instant combien de mots mémoire il reste à copier.
Le jeu d'instruction du coprocesseur périphérique est conçu pour implémenter de telles boucles. Il peut exécuter des instructions assez diverses : branchements, instructions d'accès mémoire, additions/soustractions. Ses instructions se concentrent surtout sur les accès mémoire et laissent de côté les instructions de calcul. Il ne dispose souvent que d'instructions d'addition/soustraction/comparaisons, pas d'instructions de multiplication ou de divisions. Les opérations arithmétiques complexes sont inutiles pour les tâches de gestion des périphériques, elles ne sont donc pas implémentées.
La connexion entre le coprocesseur périphérique et les contrôleurs de périphérique peut s'implémenter de deux manières. La première utilise un bus qui relie le coprocesseur périphérique et les contrôleurs de périphériques. L'implémentation demande peu de circuits et peu gérer un grand nombre de contrôleurs et de périphériques. En contrepartie que le coprocesseur ne peut communiquer qu'avec un seul contrôleur de périphérique à la fois. De tels coprocesseurs périphériques sont appelés des Selector Channel.
Une autre possibilité connecte chaque contrôleur de périphérique sur un port dédié, avec une liaison point à point dédiée. Le cout en interconnexion est grand, cela ne marche pas au-delà de quelques périphériques, mais cela permet de gérer plusieurs communications simultanées. De tels coprocesseurs IO sont appelés des Multiplexer Channel. Il est possible de les voir comme une sorte d'équivalent programmable du multiplexeur d'entrée-sortie des bus dédiés, mais pour les bus partagés.

## L'implémentation matérielle de l'espace d'adressage séparé/unifié

Intuitivement, on se dit que le bus système va de concert avec un espace d'adressage unifié, avec des entrées-sorties mappées en mémoire. De même, utiliser un bus séparé pour les entrées-sorties va de pair avec des espaces d'adressage séparés. Et dans les grandes lignes, c'est autant vrai que faux.
Un bus système peut implémenter les deux solutions d'adressage, tout dépend de comment on gère le décodage d'adresse (voir plus bas). Il en est de même que la solution avec un répartiteur, tout dépend de comment le répartiteur gère l'espace d'adressage. Par contre, deux bus séparés implique forcément un espace d'adressage séparé. Dans le sens inverse, un espace d'adressage séparé peut être réalisé par toutes les configurations, alors que les entrées-sorties mappées en mémoire impliquent forcément un bus système.
| | Espace d'adressage unifié (entrées-sorties mappées en mémoire) | Espace d'adressage séparé |
| Bus système | Oui | Oui |
| Bus séparé avec répartiteur | Oui | Oui |
| Bus séparé pour les IO | Non, sauf exceptions | Oui, obligatoire |
Il est possible d'utiliser des configurations intermédiaires, qui permettent d'implémenter des espaces d'adressages séparés ou unifiés. Mais nous verrons cela dans ce qui suit.

### Les entrées-sorties mappées en mémoire avec un bus système

Dans son implémentation la plus simple, les entrées-sorties mappées en mémoire utilisent un bus système, un bus unique pour les mémoires et les contrôleurs de périphériques. L'avantage est que cela économise beaucoup de fils, sans compter que le bit IO disparait. Par contre, impossible d'accéder à la fois à la mémoire et à un contrôleur d'entrées-sorties en parallèle.
Le principe des entrées-sorties mappées en mémoire est qu'une partie des adresses pointe vers un périphérique, d'autres vers la RAM ou la ROM. L'important est que le bon composant réponde lors d'un accès mémoire/périphérique. Si on accède à une adresse attribuée à la RAM, la RAM doit répondre, les périphériques doivent ignorer l'accès. Et inversement pour un accès périphérique.
La redirection vers le bon destinataire est faite par décodage partiel d'adresse. Pour rappel, le décodage d'adresse agit sur la mémoire ou les périphériques, et les connecte ou déconnecte du bus suivant les besoins. Pour rappel, chaque périphérique/mémoire possède une entrée CS, qui connecte ou déconnecte le composant du bus. Le circuit de décodage d'adresse prend en entrée l'adresse et commande les bits CS pour désactiver les composants non-concernés et activer la destination. Le circuit de décodage partiel d'adresse va ainsi placer le bit CS de la mémoire à 1 pour les adresses invalidées, l’empêchant de répondre à ces adresses.

### L'espace d'adressage séparé avec un bus système

Il est possible d'implémenter l'espace d'adressage séparé sans recourir à des bus séparés. Toutes les configurations de bus possibles sont compatibles avec un espace d'adressage séparé pour les IO, même un bus système unique. Mais comment faire pour l'implémenter avec un bus système ? Là encore, on utilise un système de décodage partiel d'adresse, mais qui est simplifié par rapport à celui des entrée-sorties mappées en mémoire.
Le décodage d'adresse part du principe que le bit de poids fort de l'adresse indique si l'adresse est celle d'un périphérique ou d'une mémoire. Le bit de poids fort de l'adresse, appelé le bit I/O, est mis à 0 pour une adresse mémoire, 1 pour un registre d’interfaçage. Tout cela est réalisé par l'instruction adéquate : une instruction d'accès mémoire positionnera ce bit à 0, alors qu'une instruction d'accès IO le positionnera à 1. L'adresse envoyée sur le bus est formée en récupérant l'adresse à lire/écrire et en positionnant le bit I/O à sa bonne valeur.
Un défaut de cette solution est qu'elle impose d'avoir deux espaces d'adressage de même taille, un pour la/les mémoires, un autre pour les périphériques. Pas question d'avoir un espace d'adressage plus petit pour les périphériques, alors que ce serait possible avec deux bus séparés.
Un avantage de cette méthode est qu'elle marche avec des configurations de bus un peu spéciales, qui sont intermédiaire entre des bus séparés et un bus système. Par exemple, il est possible d'avoir un bus d'adresse partagé, mais pas les autres. Ou encore, il est possible de mutualiser le bus d'adresse et de données, en conservant deux bus de commandes, un pour le périphérique et un pour la mémoire. Le bit IO fonctionne avec toutes ces configurations, la seule contrainte est que le bus d'adresse soit partagé. Mais le processeur doit gérer correctement le bus de données et envoyer les données sur le bon bus de données.

### Les entrées-sorties mappées en mémoire avec des configurations de bus spéciales

Il est possible d'implémenter les entrées-sorties mappées en mémoire sans utiliser un bus unique, avec des configurations de bus assez spéciales, dans lesquelles on a bien deux bus séparés, mais qui communiquent entre eux. Elles sont très rares, et nous en parlons ici par pur but d'exhaustivité.
La première, de loin la plus simple, consiste à accéder à la RAM d'abord, puis aux périphériques si elle ne répond pas. Une tentative d'accès en RAM fonctionnera du premier coup si l'adresse en question est attribuée à la RAM. Mais si l'adresse est associée à un périphérique, la RAM ne répondra pas et on doit retenter l'accès sur le bus pour les périphériques. L'implémentation est cependant compliquée, sans compter que les performances sont alors réduites, du fait des deux tentatives consécutives.
Les autres solutions font communiquer les deux bus pour que la RAM ou les périphériques détectent précocement les accès qui leur sont dédiés. La première solution de ce type consiste à ajouter un dispositif qui transmet les accès du bus mémoire vers le bus des périphériques. Mais le bus pour les périphériques est souvent moins rapide que le bus mémoire et l'adaptation des vitesses pose des problèmes.