Wikilivres frwikibooks https://fr.wikibooks.org/wiki/Accueil MediaWiki 1.47.0-wmf.8 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 Chimie médicinale 0 8346 768581 432618 2026-06-25T09:02:02Z Xhungab 23827 768581 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis : 29 novembre 2013 à 16:22 92.151.255.202. Sans contenu pertinent}} La '''chimie médicinale''' ou '''chimie thérapeutique''' est une discipline scientifique à l'intersection de la [[Wikilivres:Étagère chimie|chimie]] et de la [[Wikilivres:Rechercher un livre/6/61|pharmacie]] incluant la [[conception de médicaments]] et leur développement. La chimie médicinale consiste en l'identification , la synthèse et le développement de nouvelles entités moléculaires ayant une activité biologique ou thérapeutique. Elle étudie en particulier leurs propriétés biologiques et le [[rapport structure-activité]] (QSAR). La chimie pharmaceutique est hautement inter-disciplinaire puisqu'elle fait appel à la [[chimie organique]], la [[biochimie]], l'[[informatique chimique]], la [[pharmacologie]], la [[biologie moléculaire]], les [[statistiques]] et la [[chimie-physique]]. == Cheminement d'un médicament commercialisé == === Découverte === La première étape consiste en l'identification des classes de composés actifs, souvent appelés "hits", qui sont souvent trouvés en examinant systématiquement (screening) de nombreux composés aux propriétés biologiques désirées. Ces "hits" peuvent venir de sources naturelles, telles que des [[plantes]], des [[animaux]], ou des [[champignons]]. Le plus souvent, proviennent de source synthétiques, de collections de composés synthétisés et de la [[chimie combinatoire]]. Les récents développement en robotique et la miniaturisation ont grandement accéléré et automatisé le processus de screening. Typiquement, plus de 100,000 composés vont être testés individuellement avant de passer à l'étape d'optimisation. === Optimisation === La deuxième étape de la découverte d'un médicament implique la modification des molécules hits afin d'améliorer les propriétés biologiques du composé pharmacophore. La relation quantitative structure-activité du pharmacophore joue un rôle important dans la découverte des molécules "leader" qui se montrent les plus actives, selectives et avec un minimun de toxicité. === Développement === L'étape final s'occupe de la préparation des molécules "leader" pour les essais cliniques. Cette préparation implique l'optmisation de la synthèse pour leur production en grande quantité. Cette étape est réalisée classiquement par les services développement chimique pharmaceutique au sein du département CMC (chemistry Manufacturing and control) qui comprend la chimie, la formulation, l'analytique et la préparation des lots cliniques, pour en savoir plus consulter par exemple le site http://www.synthorga.fr [[Catégorie:Chimie]] kysmr4qhklkbxtzkcg075xmwx5mcd78 Sécurité des systèmes informatiques/Sécurité informatique/Le domaine SSI/Documents SSI 0 8601 768486 755722 2026-06-24T14:56:23Z Lbocquet 124070 768486 wikitext text/x-wiki <noinclude>{{Sécurité informatique}}</noinclude> == Politique de sécurité == La politique de sécurité du système informatique est de plus en plus associée à l'acronyme PSSI, pour « Politique de Sécurité du Système d'Information ». La structure générale d'une politique de sécurité peut aborder les points suivants : * Organisation et responsabilités : La PSSI précise l'organisation des fonctions chargées de la sécurité au sein de l'entreprise (postes, rattachements, répartition géographique, cumul, etc.) ainsi que les prérogatives associées à ces fonctions (conduite d'audit, ouverture des services, attribution des droits, gestion des habilitations, etc.). * Intégration et interactions de la SSI : La PSSI doit également prévoir les modalités d'intégration des fonctions SSI dans l'entreprise et notamment : ** la manière dont la SSI est prise en compte dans les projets menés par l'entreprise (notamment les projets de développement de logiciels s'il y en a) ainsi que dans les choix techniques effectués (sélection de logiciels, etc.) ; ** et la manière dont la SSI interagit avec les services chargés de l'exploitation des systèmes informatiques (priorités, indépendance ou non, acquisition des matériels, budget, etc.). * Objectifs de sécurité : La PSSI doit définir les objectifs de sécurité de haut niveau de l'entreprise. Par exemple, c'est à ce niveau que peut être imposé l'utilisation de systèmes d'authentification à deux facteurs, la nécessité de l'agrément sécurité des serveurs pour certains domaines d'activité, la prédominance de la disponibilité sur les autres aspects de la sécurité (ou l'inverse - ce qui est quand même plus rare), etc. Les objectifs de sécurité, validés par la direction générale, révèlent les intentions de l'ensemble de l'entreprise en terme de sécurité informatique et légitiment les efforts concrets de mise en place. (C'est notamment en ce sens que la PSSI est un document « politique ».) * Règles générales de sécurité : La PSSI doit non seulement identifier les objectifs assignés, mais également les règles de sécurité générales qu'elles imposent, parmi lesquelles on retrouve certains points récurrents : l'attribution d'un identifiant aux employés, la gestion de leurs habilitations, les règles de rattachement au réseau, la contractualisation des règles avec des partenaires extérieurs. Mais on peut également définir à ce niveau des règles spécifiques : la délégation de certains droits, les autorisations d'ouverture de services réseau, le type des systèmes d'authentification autorisés, la nationalité des fournisseurs, la gestion des obligations légales (traitement de données personnelles notamment), etc. * Gestion des risques : Les objectifs de sécurité correspondent à des décisions volontaires, mais celles-ci sont bien entendu motivées par les risques encourus par l'entreprise. Idéalement, les objectifs de sécurité doivent correspondre aux mesures permettant de limiter tous les risques majeurs associés à des défaillances de sécurité du système d'information. Mais des risques résiduels existent généralement et la PSSI peut aborder le sujet de la gestion des risques notamment si des efforts d'analyse des risques ou d'audit interne sont menés dans l'entreprise (c'est peut-être déjà le cas, notamment vis à vis du risque financier). Par rapport à cette structure, la PSSI peut aborder un certain nombre de thèmes correspondant aux principaux domaines techniques du système informatique et du système d'information qu'il me en œuvre. On y recense notamment les thèmes suivants : * la protection des communications (informatiques mais aussi téléphoniques) ; * la gestion des violations (blocage, arrêt, correction, suivi, voire sanction) ; * les interactions avec le domaine de la vie privée - régi en France par la loi de protection des traitements de données à caractère personnel ; * les procédures de choix et d'achats de matériels ; * la gestion de la messagerie, notamment si celle-ci est utilisée dans des cas où l'entreprise peut se trouver engagée (par exemple vis à vis d'un sous-traitant) ; * les procédures de maintenance et d'intervention sur les systèmes en exploitation ; * les modalités d'enquête et de contrôle de la sécurité ; * les règles d'identification employées dans le système d'information (les employés permanents constituent le cas le plus simple ; il est loin d'être le seul : intermittents, délégataires, machines, sous-traitants, partenaires, etc.) ; * les systèmes d'authentification associés à la SSI ; * les moyens de surveillance mise en place ; * les systèmes de contrôle d’accès utilisables ; * la manière dont les contraintes de disponibilité doivent être prises en compte ; * les règles de gestion du réseau du point de vue de la sécurité (par exemple, point d'accès unique, etc.) ; * etc. Selon nous, les caractéristiques d'une PSSI de bonne qualité sont les suivantes : * Les objectifs et les règles énoncées doivent être réalistes. Il est inutile de prescrire des obligations ou des interdictions qui gênent tellement le fonctionnement des systèmes que les utilisateurs seront obligées de les contourner pour mener à bien leur mission. * La PSSI doit être applicable, avec des moyens nécessairement limités (notamment du point de vue humain). En général, ceci impose d'accepter certains compromis de réalisation, et même certaines vulnérabilités. * La politique doit correspondre à une vision à long terme. Ce type de document ne peut pas être révisé tous les ans. Il doit donc être suffisamment générique pour rester en application quelques années. Les détails sont à préciser dans des documents dérivés. * La clarté et la concision sont nécessaires à certains moments pour énoncer des règles claires. (En général, celles-ci nécessitent toutefois plusieurs paragraphes d'explication pour être bien comprises, notamment dans différents contextes.) * La PSSI (et notamment ses règles) doit être basée sur des rôles ou des profils d'utilisateurs : les systèmes changent, la notion même d'utilisateur (au sens informatique) peut changer pour des raisons techniques, il faut s'appuyer des notions un peu plus abstraites pour définir les règles de sécurité impliquant les droits des utilisateurs. * La PSSI doit permettre une définition claire des domaines de responsabilité et d’autorité, notamment sur les systèmes techniques. L'objectif est alors de pouvoir trancher efficacement entre des points de vue contradictoires (ce qui, dans ce domaine technique, est très fréquent). * La PSSI doit être à jour (elle doit être revue périodiquement ou quand les évolutions de l'entreprise le nécessitent). C'est probablement assez difficile à assurer. À notre sens, la PSSI doit être communiquée à tout le personnel pour lui permettre de comprendre dans le détail l'impact de la SSI dans son entreprise et la manière dont il a été décidé de la gérer. Cette diffusion de la PSSI peut parfois être plus difficile à réaliser, notamment si les objectifs adoptés négligent explicitement certains risques. == Analyse des risques == Les principales étapes d'une analyse des risques sont les suivantes : # Identifier les biens et leur valeur # Attribuer des priorités aux biens # Déterminer la vulnérabilité aux menaces et les dommages potentiels # Attribuer des priorités à l’impact des menaces # Sélectionner des mesures de protections rentables La réalisation d'une analyse des risques apporte des informations très intéressantes pour la définition de la politique de sécurité. Toutefois, de notre point de vue, cette approche masque certaines des décisions qui doivent être prises pour aboutir à la définition de la politique de sécurité : la rentabilité n'est pas un critère suffisant pour décider la mise en place de certaines mesures de sécurité, par ailleurs l'évaluation des menaces et de certains dommages reste assez subjective et rend la plupart des méthodes moins mécaniques qu'elles ne l'avouent. == « Spécifications » == Les spécifications de sécurité peuvent toucher à différents sujets concernant la SSI, avec l'objectif de décrire de manière précise les règles souhaitables et leurs motivations (c'est à dire les informations à protéger) : * Clauses contractuelles : vis à vis des sous-traitants, des partenaires, etc. * Réseau : règles d'interconnexion ou d'administration, etc. * Système : règles d'administration, systèmes utilisables, etc. * Utilisateurs (finaux, administrateurs, etc.) : charte d'utilisation, etc. * Collecte des traces : cybersurveillance, protection des données, etc. * Systèmes d’authentification : protocoles autorisés, etc. * Relais : modalités de filtrage, surveillance des accès, etc. * Application (''A'', ''B'', ''C'', ''D'', etc.) : règles spécifiques de gestion pour différents types d'applications (annuaires, données financières, données bureautique, etc.). == Guides de configuration ou de recette == Les guides de configuration ou de recette identifient des points de contrôles : Ils sont déclinés précisément, par exemple par : * Système d’exploitation : ** {{w|SunOS}} 4, {{w|Solaris (système d'exploitation)|Solaris}} 2.6, 2.7, 2.9, {{w|AIX}} 4, 5, {{w|Red Hat}} 6, 7, {{w|Debian}} 2.2, 3.0, {{w|OpenBSD}} 3.5, 3.6, etc. * Serveur Web : ** [[Apache]] 2, {{w|Internet Information Services|IIS}} 6, [[Le système d'exploitation GNU-Linux/Le serveur Web Nginx|nginx]], {{w|iPlanet}}. * Équipement : ** Routeurs {{w|Cisco}} 36xx, Switches Cisco Catalyst 7000, 2900, etc. (CatOS ou IOS), {{w|Nortel}} 2430, 5430, etc. Ils couvrent des éléments de configuration et de gestion concrets, par exemple pour certains paramètres réseau des systèmes d'exploitation suivants : * Linux procfs echo "0" > /proc/sys/net/ipv4/ip_forward echo "1" > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts * (Open)BSD sysctl.conf net.inet.ip.forwarding=0 vm.swapencrypt.enable=1 * etc. == Documents de mise en service et de suivi == Après contrôle (réussi ou non) de la sécurité d'un système, un document de mise en service identifiant les non-conformités constatées et les vulnérabilités résiduelles du système concrétise l'autorisation de mise en service du système et doit permettre par exemple l'ouverture des accès réseau. Des failles peuvent également être constatées a posteriori par des contrôles de sécurité. Dans chacun de ces cas, le suivi de la sécurité doit s'appuyer sur des documents identifiant les problèmes résiduels connus et permettant de maintenir ou de faire progresser la sécurité des différents systèmes. On trouve notamment parmi ces documents des matrices de conformité ou des fiches de suivi. 5ny8qlt2r81ohjp87ikcc4ydq1widlm Chimie physique/Introduction à la thermodynamique classique 0 36097 768580 639426 2026-06-25T08:53:41Z Xhungab 23827 768580 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis : 31 mars 2010 à 23:51 Meitnerium. Sans contenu pertinent}} La thermodynamique s'intéresse aux phénomènes qui sont considérés à l'équilibre, sans se préoccuper de la dépendance sur le temps comme en cinétique. La thermodynamique parle en terme d'énergie, d'entropie, de volume, chaleur, efficacité, énergie libre, potentiel chimique, pression, température, etc. Elle a été développé pour expliquer les machines à vapeur depuis les années 1800. [[Catégorie:chimie]] [[en: Physical Chemistry/Introduction to Thermodynamics]] flvdoqoahbjwvmblev9ohtoliy3mw8o Chimie physique/Fonction d'état 0 36098 768579 347148 2026-06-25T08:51:41Z Xhungab 23827 768579 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis :1 avril 2010 à 00:21 Meitnerium. Sans contenu pertinent}} [[w:Fonction d'état|Une fonction d'état]] est une fonction des variables d'état qui décrivent les états d'équilibre d'un système thermodynamique. [[Catégorie:chimie]] [[en:Physical Chemistry/State Functions]] 4013kmwll06mc4r5vvf2sj6nyjb1tjo Plantes messicoles de l'Avesnois/Plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus 0 47755 768586 353084 2026-06-25T09:12:06Z Xhungab 23827 768586 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis : 22 janvier 2012 à 05:04 Christian COGNEAUX. Sans contenu pertinent}} '''Cette page est une copie non terminée de cette page.''' [[Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Scleranthus annuus L. subsp. annuus|Scleranthus annuus L. subsp. annuus]] Gnavelle annuelle - Scléranthe annuel Famille des Illecebracées == Description == Plante annuelle de 5-20 cm., verte, à racine grêle ; tiges grêles, étalées-ascendantes, pubescentes ; feuilles linéaires allongées en alène ; fleurs verdâtres sans pétales, en fascicules axillaires et terminaux plus ou moins lâches ; calice fructifère (4 à 4 1/2 mill.), glabre, à tube non contracté au sommet, atténué à la base, égalant les sépales ; sépales lancéolés-linéaires atténués en pointe droite, à marge membraneuse étroite, écartés et un peu ouverts après la floraison. == Écologie == C'est une annuelle commensale des cultures acidophiles appréciant les sols sableux, voire argileux. [[Catégorie:Plantes messicoles de l'Avesnois (livre)]] ry85gj4eg1iqtzkqxf60w3807wdopor Plantes messicoles de l'Avesnois/Atlas des plantes messicoles/Présentation des espèces/Chondrilla juncea L. 0 47889 768588 564480 2026-06-25T09:19:55Z Xhungab 23827 768588 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis : 3 février 2012 à 11:48 83.192.196.125. Sans contenu pertinent}} '''Cette page n'est pas relié au livre [[Plantes messicoles de l'Avesnois|Plantes messicoles de l'Avesnois]], La qualité du contenu de la feuille ne correspond pas à celle du livre.''' [[Image:Nsr-slika-274.png|thumb|upright=1.6| ''[[:w:Chondrilla juncea]]'' ; [[:w:Planche botanique|Planche botanique ancienne]] ]] <strong>'''Chondrille effilée, Chondrille à tiges de jonc '''</strong> <br />(Hogbite, Naked Weed, Rush Skeletonweed pour les anglophones et [[w:nl:Biesknikbloem]] pour les néerlandophones) <br />Famille des [[:w:Asteraceae]] [[Catégorie:Plantes messicoles de l'Avesnois (livre)|Aphanes arvensis L.]] o0zq1x9ilem0a5sir5ofuxey7tew7kj Le système d'exploitation GNU-Linux/Sommaire 0 48967 768484 659055 2026-06-24T14:54:36Z Lbocquet 124070 768484 wikitext text/x-wiki <div style="margin: 0.1em 0px 0px -1em;"> # [[Le système d'exploitation GNU-Linux/Qu'est-ce qu'un système d'exploitation ?|Qu'est-ce qu'un système d'exploitation ?]] # [[Le système d'exploitation GNU-Linux/Unix et Linux|Unix et Linux]] # [[Le système d'exploitation GNU-Linux/Partitionnement du disque|Partitionnement du disque]] # [[Le système d'exploitation GNU-Linux/Installation|Installation]] ## [[Le système d'exploitation GNU-Linux/Installer Debian via le réseau|Installer Debian via le réseau]] # Utilisateur Unix, l'interface console ## [[Le système d'exploitation GNU-Linux/Le login|Le login]] ## [[Le système d'exploitation GNU-Linux/Variables d'environnement|Variables d'environnement]] ## [[Le système d'exploitation GNU-Linux/Commandes de base|Commandes de base]] ## [[Le système d'exploitation GNU-Linux/L'aide en ligne man|L'aide en ligne man]] ## [[Le système d'exploitation GNU-Linux/L'éditeur de texte vi|L'éditeur de texte vi]] ## [[Le système d'exploitation GNU-Linux/Les shells|Les shells]] ## [[Le système d'exploitation GNU-Linux/La complétion|La complétion]] ## [[Le système d'exploitation GNU-Linux/Les jokers|Les jokers]] ## [[Le système d'exploitation GNU-Linux/Les répertoires importants|Les répertoires importants]] ## [[Le système d'exploitation GNU-Linux/Redirection des entrées/sorties|Redirection des entrées/sorties]] ## [[Le système d'exploitation GNU-Linux/Invoquer un programme en tâche de fond|Invoquer un programme en tâche de fond]] ## [[Le système d'exploitation GNU-Linux/Propriétaires et droits d'accès|Propriétaires et droits d'accès]] ## [[Le système d'exploitation GNU-Linux/Processus|Processus]] ## [[Le système d'exploitation GNU-Linux/Locale|Locale]] # Administration du système ## [[Le système d'exploitation GNU-Linux/Configuration du réseau|Configuration du réseau]] ## [[Le système d'exploitation GNU-Linux/Les utilisateurs et groupes|Les utilisateurs et groupes]] ## [[Le système d'exploitation GNU-Linux/Le processus d'initialisation|Le processus d'initialisation]] ## [[Le système d'exploitation GNU-Linux/Les systèmes de fichiers|Les systèmes de fichiers]] ## [[Le système d'exploitation GNU-Linux/Le système virtuel /proc|Le système virtuel /proc]] ## [[Le système d'exploitation GNU-Linux/Les périphériques /dev|Les périphériques /dev]] ## [[Le système d'exploitation GNU-Linux/L'ordonnanceur de travaux cron|L'ordonnanceur de travaux cron]] ## [[Le système d'exploitation GNU-Linux/Le backup : tar et gzip|Le backup : tar et gzip]] ## [[Le système d'exploitation GNU-Linux/ghost avec partimage|ghost avec partimage]] ## [[Le système d'exploitation GNU-Linux/sauvegarde de fichiers avec rsync|sauvegarde de fichiers avec rsync]] ## [[Le système d'exploitation GNU-Linux/Les fichiers journaux syslog|Les fichiers journaux syslog]] ## [[Le système d'exploitation GNU-Linux/Installation de nouveaux logiciels|Installation de nouveaux logiciels]] ## [[Le système d'exploitation GNU-Linux/Le noyau Linux et les modules|Le noyau Linux et les modules]] ## [[Le système d'exploitation GNU-Linux/Autres commandes utiles|Autres commandes utiles]] ## [[Le système d'exploitation GNU-Linux/Installation RAID1 logiciel + LVM + XFS|Installation RAID1 logiciel + LVM + XFS]] ## [[Le système d'exploitation GNU-Linux/Scripts de surveillance|Scripts de surveillance]] ## [[Le système d'exploitation GNU-Linux/Réseaux sans fil|Réseaux sans fil]] # Administration des services réseaux ## [[Le système d'exploitation GNU-Linux/L'outil d'administration Webmin|L'outil d'administration Webmin]] ## [[Le système d'exploitation GNU-Linux/telnet|telnet]] ## [[Le système d'exploitation GNU-Linux/cURL|cURL]] ## [[Le système d'exploitation GNU-Linux/wget|wget]] ## [[Le système d'exploitation GNU-Linux/Le serveur de noms BIND|Le serveur de noms BIND]] ## [[Le système d'exploitation GNU-Linux/Le serveur de configuration réseau DHCP|Le serveur de configuration réseau DHCP]] ## [[Le système d'exploitation GNU-Linux/Le serveur de shell distant SSH|Le serveur de shell distant SSH]] ## [[Le système d'exploitation GNU-Linux/Le partage de fichiers Samba|Le partage de fichiers Samba]] ## [[Le système d'exploitation GNU-Linux/Le partage de fichiers NFS|Le partage de fichiers NFS]] ## [[Le système d'exploitation GNU-Linux/Le serveur d'impression CUPS|Le serveur d'impression CUPS]] ## [[Le système d'exploitation GNU-Linux/Le serveur de fichiers FTP|Le serveur de fichiers FTP]] ## [[Le système d'exploitation GNU-Linux/Le serveur Web Apache|Le serveur Web Apache]] ## [[Le système d'exploitation GNU-Linux/Le serveur Web Nginx|Le serveur Web nginx]] ## [[Le système d'exploitation GNU-Linux/La base de données MySQL|La base de données MySQL]] ## [[Le système d'exploitation GNU-Linux/Memcached|La base de données Memcached]] ## [[Le système d'exploitation GNU-Linux/Redis|La base de données Redis]] ## [[Le système d'exploitation GNU-Linux/Le serveur de mails Postfix|Le serveur de mails Postfix]] ## [[Le système d'exploitation GNU-Linux/Les annuaires LDAP|Les annuaires LDAP]] ## [[Le système d'exploitation GNU-Linux/La supervision|La supervision Nagios]] # Sécurisation d'un serveur Linux ## [[Le système d'exploitation GNU-Linux/Installation d'un service en mode chroot|Installation d'un service en mode chroot]] ## [[Le système d'exploitation GNU-Linux/Protection avec iptables|Protection avec iptables]] #[[Le système d'exploitation GNU-Linux/Erreurs connues|Erreurs connues]] #[[Le système d'exploitation GNU-Linux/Médiagraphie|Médiagraphie]] #[[Le système d'exploitation GNU-Linux/Auteurs|Auteurs]] </div> {{AutoCat}} 1aiqekshmsmxxeu29mqa15dnl3i7y3t Ict@innovation: Free your IT Business in Africa/2-Others 0 53229 768589 674944 2026-06-25T09:35:44Z Wisdood 66322 768589 wikitext text/x-wiki == Études de cas complémentaires : Exemples d'Afrique du Sud et Australe == === a) L’Expérience d’OpenWorld Ltd === {| style="border-spacing:0;" | style="border-top:0.05pt solid #000000;border-bottom:0.05pt solid #000000;border-left:0.05pt solid #000000;border-right:none;padding:0.097cm;"| [[Image:Ict-innovation-FBT-Fig-2-1.png]] | style="border:0.05pt solid #000000;padding:0.097cm;"| Raison Sociale: OPENWORLD LTD Fondé en : 2004 Effectif : 7 Pays : Kenya Site Web : [http://www.openworld.co.ke/ http://www.openworld.co.ke] Type d’Entreprise : Développement de logiciels libres et Formation |} ==== Résumé ==== OPENWORLD LTD est une entreprise centrée sur la communauté, qui offre un large éventail de services professionnels et techniques utilisant exclusivement les technologies open source. La vision de l'entreprise est d’être un leader du marché dans la fourniture et le support de l’open source auprès des diverses entreprises publiques et privées, du gouvernement et des PME au Kenya et dans la plus grande partie de la région Orientale d’Afrique. L’entreprise a été fondée en 2004 et emploie actuellement 7 personnes. ==== Introduction ==== OPENWORLD LTD a commencé à partir des économies du propriétaire et des investissements antérieurs. Le capital initial de la société s’est développé au fil du temps grâce aux revenus générés par le conseil, le développement et les ventes de logiciels de solutions open source, mais à la formation dans divers modules open source. La démarche stratégique d’OpenWorld était de commencer à petite échelle, d’encourager les partenariats et d’externaliser des services non essentiels liés à certains aspects opérationnels de l’entreprise. En adoptant un modèle de service pour l’entreprise, les dépenses en capital ont été réduites aussi. La survie d’OpenWorld et une tentative de gagner un avantage concurrentiel en faisant des affaires autour de logiciels open source reflètent les réalités du marché local au Kenya. Certains de ces défis sont inhérents à toute entreprise, qu’elle soit open source ou propriétaire. Le besoin de développer la clientèle s’est traduit par la nécessité de penser en dehors de la boîte et d’aller au-delà des pratiques traditionnelles qui consistent à «attendre que les clients viennent à vous ». En ce qui concerne l’infrastructure, la société avait dû payer pour avoir ses bureaux actuels. Pour l’enregistrement et les documents d’entreprise nécessaires, il était impérieux pour OpenWorld de conclure un contrat avec un cabinet d’avocats et un expert-comptable pour accompagner le processus. Le recrutement d’employés a nécessité la définition de la culture d’entreprise à partir de zéro ainsi que former des individus et inculquer la vision de la société d’open source et l’ouverture d’esprit. ==== Principale activité de logiciel libre et open source ==== En organisant des formations sur les logiciels libres et open source pour leurs clients et des ateliers réguliers, et en participant à des discussions sur invitation, etc. les dirigeants de la société ont réussi à informer d’autres entreprises et des cadres supérieurs qui n’étaient pas familiers avec les logiciels libres et open source sur les avantages du passage à l’open source. Ils ont ainsi démontré leur capacité de communication sur les services et produits qu’ils proposent. ==== Services ==== OpenWorld offre à sa clientèle divers services dans les domaines suivants : Conseil, Formation, développement de produit et accompagnement. Les trois derniers (décrits par les directeurs de l’entreprise qui sont la formation, les Systèmes et les Applications) rapportent à peu près des parts égales de recettes pour OpenWorld Ltd. L’entreprise déploie et soutient plus de 11 solutions d’entreprise, allant de serveurs (courrier, liste, fichier, impression, et base de données, etc.) aux Firewalls (pare-feux) aux Systèmes de Détection d’Intrusion et l’hébergement de site Web. Pour toutes ses solutions, OpenWorld adopte les logiciels open source et le modèle open source. La plupart des applications sont basées sur le Web et sont construites sur LAMP (Linux, Apache,MySQL et Perl, Python, ou PHP) ou le cadre Zope Application Server. L’infrastructure de la plateforme de la société est toujours basée sur UNIX ou Linux. Mais pour répondre aux besoins de ses clients, la stratégie d’OpenWorld Ltd est de proposer des services intégrés puisque la plupart de ses clients sont à la recherche de solutions totales ou de magasins où l’on trouve de tout. Les produits et services de l'entreprise sont commercialisés à travers des ventes directes, la publicité dans les médias locales, des campagnes de sensibilisation, et la formation mais aussi à travers des manifestations locales (Salons TIC, par exemple). ==== Les leçons tirées ==== Il pourrait y avoir d’autres entreprises au Kenya ou en dehors qui offrent les mêmes services et produits que OpenWorld. Cependant, ce qui a bien fonctionné pour l’entreprise au fil des ans, comme le directeur de l’entreprise le dit c’est, {{g|Essayer ce qui n’a pas été testé, et être toujours prêt à faire les choses différemment en termes de réponse aux demandes des clients}}. ==== Conclusion ==== Une chose pour laquelle l’entreprise se passionne et qu’elle aimerait faire dans un avenir lointain c’est la localisation de ses produits. Le Président Directeur Général a déclaré que « OPENWORLD ne s’occupe d’aucune localisation pour le moment. Je ne vais pas l’exclure dans l’avenir mais ce n’est pas une activité qui figure dans nos plans immédiats. Cependant, je suis passionné pour la transcription dans ma langue locale et si le temps le permet, je peux personnellement m’engager dans une telle activité.» === b) Le cas de GIS Global Image Ltd. === {| style="border-spacing:0;" | style="border-top:0.05pt solid #000000;border-bottom:0.05pt solid #000000;border-left:0.05pt solid #000000;border-right:none;padding:0.097cm;"| [[Image:Ict-innovation-FBT-Fig-2-2.png]] | style="border:0.05pt solid #000000;padding:0.097cm;"| Raison sociale: GIS Global Image (PTY) Ltd. Fondée en : 2000 Effectif : 17 Pays : Afrique du Sud Site Web&nbsp;: [http://www.globalimage.co.za/ http://www.globalimage.co.za] Type d’entreprise : Ventes et assistance produits SIG de logiciels libres et open source |} ==== Résumé ==== GIS Global Image (PTY) Ltd est une société basée en Afrique du Sud, qui se spécialise en Systèmes d’Information Géographiques (SIG). Essentiellement un SIG se sert de logiciels pour afficher les données de base sur une carte, qui peut aider les décideurs. La société se concentre sur les besoins des clients et utilise le logiciel le plus pertinent pour répondre à ces besoins. Pour y parvenir, GIS Global Image adopte un modèle hybride – fournissant des solutions et services logiciels à la fois autour de logiciels open source et logiciels propriétaires. GIS Global Image réalise des prestations de conseil relatifs aux systèmes d’information géographique (SIG) ; cela implique la conception et l’implémentation de systèmes, l’acquisition et l’analyse de données, des ateliers de formation et le renforcement des capacités. L'entreprise a deux succursales en Afrique du Sud : à Prétoria et à Stellenbosch et emploie actuellement 17 employés, dont 5 administrateurs. Ce réseau est élargi grâce à des partenariats stratégiques en Afrique australe et orientale. ==== Introduction ==== GIS Global Image (PTY) Ltd est le résultat de la fusion de deux sociétés (Urban Dynamics GIS et Plandata) en 2000, lui donnant l’avantage d’une infrastructure et base de données client existante et établie. Depuis ses débuts, GIS Global Image se concentre sur la prestation de services liés à la conception de systèmes et à la mise en œuvre ; l’acquisition de données et l’analyse ; les ateliers de formation et le renforcement des capacités. Ces systèmes sont généralement basés sur des plateformes Internet et Intranet, rendant les informations accessibles à une large base d’utilisateurs dans les organisations. Traditionnellement, les services sont rendus aux organisations privées et publiques, qui visent à présenter leurs données organisationnelles sur les systèmes d’information géo-spatiaux basés sur la cartographie. Ces clients comprennent tous les niveaux hiérarchiques de la haute administration (Gouvernement National ; Gouvernements Provinciaux et Locaux) ; les sociétés paraétatiques dont la Commission pour l’Approvisionnement en Électricité d’Afrique du Sud (ESKOM) ; le Conseil pour la Recherche Scientifique et Industrielle (CSIR) et diverses compagnies privées qui mettent en œuvre ces progiciels cartographiques afin de prendre des décisions éclairées. GIS Global Image s’engage davantage dans des partenariats stratégiques avec d’autres sociétés et institutions pédagogiques. Ces partenariats renforcent la capacité afin de fournir des services aux clients. Nous pouvons noter comme exemple le partenariat stratégique avec African eDevelopment Resource Centre (AeRC), qui est basée à Nairobi au Kenya, pour animer les ateliers pratiques sur les SIG et dispenser de la formation en SIG. Dans la même région, GIS Global Image a également un accord avec Organizational Management Systems (OMS), basée également à Nairobi, pour mettre en œuvre les Systèmes Géographiques dans la région d’Afrique orientale. D’autres partenariats existent aussi avec Maluleke, Luthuli & Associates, et une entreprise de planification du développement en Afrique du Sud, qui détient aussi des actions au sein de GIS Global Image. L’Activité principale de l’Entreprise basée sur les logiciels libres et open source ==== Les produits et services ==== L’activité principale de Global Image offre une variété de Systèmes d’Information Géographique ou des produits et services SIG pour permettre aux autorités locales ou les municipalités d’accéder et de traiter l’information à partir des données SIG. Ce qui a bien fonctionné pour GIS Global Image au cours des années c’est l’utilisation d’outils open source pour la formation de ses clients. La société organise souvent des ateliers de formation de 1 à 3 jours au cours desquels il effectue un transfert de compétences pratiques en SIG. Les participants, qui peuvent être des clients potentiels, apprennent aussi comment utiliser les produits phares open source de l’entreprise tels que le Papyrus. Les ateliers sont ouverts à tous mais les participants paient des frais pour couvrir les coûts pour l’impression du matériel de formation; aliments et boissons ; location de salle et du matériel ; les frais du formateur. Le produit de Papyrus, qui est basé sur des composants open source, peut être utilisé pour intégrer les informations provenant de divers secteurs au sein d’un département ou district d’un gouvernement local. La composante cartographie SIG de Papyrus est distribuée comme logiciel libre sous licence GPL. Le produit GIS@School est (actuellement) un logiciel propriétaire qui est utilisé par certaines écoles. La société envisage d’utiliser MapWindow GIS ou Quantum GIS – tous les deux Progiciel SIG – comme le logiciel de cartographie. ==== Avantages acquis grâce à des logiciels libres et Open source ==== Comment est-ce que GIS Global Image Ltd. bénéficie des logiciels libres et open source? L’entreprise développe son propre logiciel, qui est fourni comme « Logiciel à télécharger gratuitement», bien qu’ils aient conservé le code source, et personnaliseraient le logiciel en fonction de la demande. L’entreprise fait plus de personnalisation que de développement de nouveaux logiciels SIG. Cependant, la personnalisation du logiciel de la plupart des produits de la société n’implique pas la modification du noyau du logiciel, mais seulement la programmation OLE pour répondre aux besoins des clients. Les développeurs de logiciels d’entreprise ne sont pas directement impliqués dans un projet open source quelconque, mais s’ils personnalisent le logiciel qu’ils utilisent, l’entreprise se charge de remettre le logiciel personnalisé à la communauté, comme prévu dans l’accord de licence. Selon les propos du Directeur de la société, l'avantage du logiciel open source est de donner à l’entreprise “l’accès au logiciel développé et maintenu par une communauté de développeurs plus large». Ainsi, l’open source agit comme un catalyseur pour réduire les coûts du développement et de maintenance du logiciel pour une entreprise relativement petite comme GIS Global Image. GIS Global Image commercialise ses produits et services en utilisant le marketing par Internet, la méthode de bouche-à-oreille, des articles dans des publications locales, à travers les ateliers et la formation. La compagnie utilise une autre stratégie aussi - le marketing par appel d’offres. GIS Global Image doit soumissionner pour la plupart de leurs services, là où ils utilisent leurs produits. Cela signifie que la société accède à des demandes d’offres et grâce à l’appel d’offres, ils sont en mesure de présenter ou de commercialiser leurs produits. ==== Les défis dans la conduite des affaires en logiciels libres et open source ==== Malgré le fait de bénéficier d’une infrastructure et d’une base de clients existantes, l’expérience de GIS Gobal Image a encore beaucoup à apprendre aux PME du secteur des logiciels libres et open source et qui opèrent en Afrique du Sud. Le rapport concernant l’expérience de l'entreprise peut être synthétisé comme suit: * Financement: Beaucoup d’institutions financières sont réticentes à financer les PME. L’Afrique du Sud a mis en œuvre le Financial Intelligence Centres Act (La Loi sur les Centres de Renseignements Financiers) en 2001. L’un des objectifs de cette loi est de mettre fin au blanchissement d’argent mais en même temps d’imposer des restrictions sur l’octroi de crédit aux entreprises. La solution possible de GIS Global Image dans ce climat est de financer des activités par leurs propres ressources, mais cela peut également entraver le flux de trésorerie. Une autre solution possible pourrait être de conclure des partenariats afin qu’une entreprise soit en mesure de partager des ressources pour développer des progiciels, et ainsi partager les risques. * Ressources humaines : GIS Global Image a eu à expérimenter le fait qu’il est parfois difficile de retenir les meilleurs employés, surtout dans les marchés où les secteurs des services et les entreprises sont en concurrence pour avoir le personnel qualifié rare. * Offre de service: Travailler avec les départements gouvernementaux, mettre en œuvre les solutions SIG basées sur l’open source est souvent difficile, car les clients sont parfois sceptiques puisqu’ils ne connaissent que les solutions propriétaires telles qu’ESRI ArcGIS. Souvent, certains reconnaissent que les frais de licences sont excessivement élevés, ce qui crée des opportunités pour des solutions basées sur l’open source. A cet égard, la position de GIS Global Image apparaît plus avantageuse. * Ne vous concentrez pas seulement sur la solution, mais éduquez vos clients aussi. Éduquer le client va au-delà de la simple formation ou de la livraison/affichage de documentation ; c’est l’éducation au sujet du sens du concept open source. Par exemple, GIS Global Image montre que certains de leurs clients dans les démembrements de l’administration locale ne réalisent pas que l’Afrique du Sud a adopté une stratégie open source en 2007. En informant les administrations locales à ce sujet, l’entreprise éduque ses clients au fur et à mesure qu’ils font la promotion de leurs produits et services. ==== Facteurs-clés pour utiliser avec succès le logiciel libre et open source ==== L’expérience de GIS Global Image en matière de création d’entreprise autour de logiciels libres et open source en Afrique en général et en Afrique du Sud en particulier sert de conseils utiles: * En Afrique en général, il y a une problématique d’accès aux nouvelles technologies. Dans la plupart des pays l’accès à l’Internet est souvent bien limité, s’il est disponible ; il est souvent lent et disponible dans des locaux tels que les Cyber Cafés, Télé-centres ou hôtels. Toutefois, au fur et à mesure que l’Afrique évolue et se trouve en position de compétition dans cette ère numérique, plusieurs gouvernements, opérateurs de télécommunication, quelques projets (AfriNic, par exemple) tentent d’aborder cette question. L’expérience de GIS Global Image permet d’affirmer que «les gens pourraient ne pas avoir accès à l’ordinateur individuel ou à l’Internet, mais ils ont des appareils mobiles (téléphones, assistants numériques personnels –PDA, etc.)». C’est ici que le Président-directeur général, Nico Elema, “pense que le marché consiste à fournir des applications pour accéder aux informations à travers ces appareils mobiles ». * Créer une entreprise personnelle et professionnelle en ligne et sur le World Wide Web à l’aide d’un logiciel social. Un profil professionnel personnel peut être développé à travers des réseaux sociaux tels que Linkedln ( www.linkedin.com) et on peut faire la promotion d’une entreprise à travers le site Web de société, les flux RSS, les blogs du personnel, etc. * S’impliquer dans des projets de logiciels libres et open source et dans des discussions sur les listes de diffusion, forums, conférences, etc. L’expérience de GIS Global Image est que ce ne sont pas tous ceux qui sont des développeurs de logiciels qui peuvent contribuer au code du logiciel, mais beaucoup peuvent parvenir à connaître les logiciels et les systèmes si bien grâce à l’utilisation constante et à l’auto-apprentissage. De tels individus peuvent fournir la formation et le soutien dans le logiciel. Cela va créer le marché de logiciels libres et open source, qui peut conduire à un meilleur développement de logiciels. Si vous êtes en mesure de contribuer aux discussions sur l’open source et de développer un profil en ligne, ce qui indique que vous êtes un spécialiste soit en codage, formation ou soutien, vous pourriez avoir une meilleure chance d’être impliqué dans les projets et d’élargir votre base d’activité et de clientèle. * Les clients ne se soucient pas souvent du logiciel utilisé, mais de la solution et s’il est en mesure de répondre à leurs besoins ou pas. Souvent, les débats se focalisent trop sur les détails techniques et non pas sur la solution. * L’Afrique est extrêmement diverse en termes de culture. Ce qui fonctionne dans d’autres régions du monde, pourrait ne pas fonctionner comme une copie carbone en Afrique. Les connaissances locales et l’expérience sont donc essentielles, afin de comprendre le marché. En Afrique du Sud en général, tous les éléments ci-dessus seront évidemment applicables. Développer des solutions qui sont basées sur l’Internet donnera l’accès à de nombreuses personnes, mais là encore l’expérience de GIS Global Image montre que l’avenir réside peut-être dans les technologies mobiles. Beaucoup considèrent l’Afrique du Sud comme un leader dans l’open source comme indiqué par l’adoption de la stratégie du logiciel open source par le Parlement de l’Afrique du Sud. Partant de l’expérience de GIS Global Image, le Président Directeur Général suppose que toute entreprise ou individu faisant des affaires dans le domaine de l’open source en Afrique du Sud doit utiliser cette initiative du gouvernement comme un levier, surtout s’il fait des affaires avec le gouvernement. ==== Le modèle de génération de revenus ==== La majeure partie des recettes de l'entreprise provient des ventes et des services de maintenance de son produit phare, Papyrus Spatial MIS, avec les Services SIG (Conseil, par exemple) en deuxième position. Actuellement, peu de revenus sont générés au moyen des ateliers et la formation où l’on utilise le logiciel SIG, mais il est prévu que ce volet s’élargisse à l’avenir. ==== Travail en réseau ==== L’une des pierres angulaires de l’entreprise est construite sur les relations clients et les réseaux. Être dans une position où les clients se fient au prestataire de services pour qu’il réponde aux besoins des utilisateurs, est un atout précieux pour l’organisation. Établir des relations avec les clients là où ils sont en mesure de faire le marketing de bouche-à-oreille est l’une des méthodes les plus précieuses en marketing. Afin de maintenir cette relation de travail en réseau, les clients existants jouissent d’accès préférentiel aux annonces de nouveaux modules et de logiciels. Les clients existants ont également des taux préférentiels lorsque les ateliers en SIG, auxquels ils veulent participer sont présentés. ==== Leçons retenues ==== Dans un marché où il y a des fournisseurs de logiciels propriétaires prédominants (Logiciel ESRI ArcGIS) les besoins individuels doivent être évalués pour déterminer si cette organisation spécifique est prête à être « convertie » en solutions de SIG basées sur les logiciels libres et open source. Souvent la réponse réside dans la mise en œuvre d’une solution hybride où les éléments de logiciels propriétaires et logiciels libres et open source sont utilisés, ce qui offre également des avantages aux organisations. Les SIG basés sur les logiciels libres et open source sont plus facilement introduits là où les utilisateurs n’ont pas d’expérience antérieure dans logiciels SIG, et ne se soucient réellement pas de la technologie, mais de la solution qui peut être apportée à travers le logiciel. === c) FutureSoft : Redynamiser les ressources des logiciels par le biais de logiciels libres et open source === {| style="border-spacing:0;" | style="border-top:0.05pt solid #000000;border-bottom:0.05pt solid #000000;border-left:0.05pt solid #000000;border-right:none;padding:0.097cm;"| [[Image:Ict-innovation-FBT-Fig-2-3.png]] | style="border:0.05pt solid #000000;padding:0.097cm;"| Raison Sociale: Future Software Resources Nigeria Ltd. Fondée en : 1998 Effectif : 5 Pays : Nigéria Site Web : [http://www.futuresoft-ng.com/ http://www.futuresoft-ng.com] Type d’Entreprise : Les solutions d’Entreprise en logiciels libres et open source |} ==== Résumé ==== Future Software Resources Nigeria Ltd. est un nouveau fournisseur de solutions Web clé en main opérant au Nigéria en partenariat avec DigiRev LLC (USA) et Paperless Staffroom Ltd (Royaume Uni). La société a été fondée en 1998, mais est restée inactive jusqu’au début de 2008 lorsque la direction a complètement changé la marque et a changé l’orientation vers la conception de sites Web, l’hébergement de sites Web, et la création de solutions logicielles qui sont essentiellement basées sur les logiciels open source. Actuellement FutureSoft a 5 employés à plein temps. Des consultants et développeurs de logiciels travaillent également avec l’entreprise sur une base contractuelle. ==== Introduction ==== La mission de Future Software Resources Nigeria Ltd. est de fournir une gamme de services qui permettent aux petites, moyennes et grandes entreprises ainsi qu’aux individus d’héberger leurs sites Web sur le World Wide Web. La compagnie fournit des services relatifs à l’enregistrement de nom de domaine, au développement et à l’hébergement de sites Web, développement et maintenance de commerce en ligne et applications pour le Web. L’expérience fondamentale de FutureSoft est d’essayer de maintenir les coûts à un minimum en réutilisant les composants open source et en externalisant le développement des activités essentielles. La société a connu un début favorable dû au fait qu’elle a évité de prendre des crédits bancaires et détenait le privilège d’avoir des bureaux sans payer de loyer. Les procédures d’enregistrement de la société n’étaient pas difficiles. Néanmoins, le Président-Directeur Général a indiqué que « la meilleure chose à faire est de passer par un cabinet d’avocats ». Au cours de ses années d’exploitation, les problèmes que la compagnie avait connus étaient liés à la recherche de main-d’œuvre qualifiée qui est prête à travailler pour un bas salaire. Selon le Président Directeur Général, la solution de l’entreprise à ce problème consiste à externaliser la majeure partie de leur travail en Inde, où la société a trouvé des développeurs hautement qualifiés, qui sont disposés à travailler à des taux très bas. ==== Vision d’Affaires en logiciels libres et open source ==== La stratégie de l’entreprise était de réduire les principaux coûts de démarrage liés au développement du site Web, la conception et l’impression de leurs propres cartes de visite, les papiers à en-tête, les factures et les reçus. Qu’est-ce qui a bien fonctionné pour Future Software Resources Nigeria Ltd.? En regardant d’autres entreprises au Nigéria et en dehors qui offrent des services similaires, l’entreprise est bien positionnée pour concurrencer et bénéficier des produits de logiciels libres et open source et des services car, comme le Directeur l’a indiqué, « nous respectons les délais et fournissons des produits et des services de qualité. La compagnie s’appuie non seulement sur son IT et les compétences de son personnel, mais nous travaille également avec des consultants, qui sont en mesure d’offrir, à temps, des logiciels de travail ou solutions dont les clients ont besoin; à partir de la rédaction de documents jusqu’à l’élaboration de stratégies de marketing conventionnelles et en ligne, des conceptions graphiques, le développement de contenu, etc. ==== Travail en réseau ==== Ayant acquis l’expérience en création d’entreprise autour de logiciels libres et open source en Afrique en général, et au Nigéria en particulier, Future Software Resources Nigeria Ltd. propose des conseils aux PME faisant des affaires autour de logiciels open source pour qu’elles envisagent les mesures suivantes : * Identifier les besoins du marché cible. L’étude de marché est la clé. Il n’y a pas de sens de développer une solution dont personne n’a besoin. * Localiser la solution afin de la rendre performante pour le marché cible. La localisation peut aller du contenu jusqu’aux fonctionnalités réelles. Avec une planification correcte, la société voit open source comme un grand moyen de génération de richesse. * S’assurer que les fonctionnalités du logiciel sont testées avant de le déployer chez un client potentiel. La recherche de différents types de logiciels qui font la même chose est très importante. S’engager et comprendre le projet de logiciel libre et open source que vous personnalisez, distribuez ou localisez . ==== Le modèle de génération de revenus ==== Parmi les produits et services de l’entreprise, les solutions d’entreprises génèrent une grande partie des recettes. Toutefois, la société envisage de publier une série de solutions pédagogiques dans un avenir proche, qui, espère-t-elle, pourront correspondre, si non dépasser, les flux de revenus actuellement générés par les solutions d’entreprises. La compagnie s’active également sur la création d’une marque forte, une gamme de produits, ainsi que l’augmentation du personnel. ==== Leçons retenues ==== La participation à des projets open source et les communautés: Future Software Resources Nigeria Ltd. s’est rendu compte que pour s’aventurer dans le business des logiciels libres et open source, une société doit avoir des Hommes ayant une vaste expérience et de bonnes références quant à la participation à des projets libres et open source et l'interaction avec les communautés. L’entreprise a un expert en logiciel libre et open source, qui a fait beaucoup de recherches sur les projets open source avant de rejoindre l'entreprise. Il est chargé de tester différentes solutions open source et de trouver, selon le Président-directeur général, «les meilleurs modèles de fonctionnement pour nos clients dans le marché africain. L’entreprise évalue aussi les développeurs chez qui elle externalise le développement de solutions en s’assurant que ces derniers ont de bonnes références par rapport aux projets libres et open source et les communautés. ==== Conclusion ==== La société partage également ses efforts en matière de personnalisation avec les communautés et les développeurs qui veulent mettre en œuvre des personnalisations similaires. Geste de bonne volonté, Future Software Resources Nigeria Ltd. fait don d’une partie des ses bénéfices à certains projets open source et communautés, dont l’entreprise souhaite taire les noms. Ce qui manque présentement à l’arsenal open source de la société c’est une politique complète open source. A cet égard, le Président-directeur général a déclaré: “Nous travaillons actuellement pour mettre sur pied une politique open source”. === d) Linux Holding (Pty) Ltd : Former les Utilisateurs de Linux en Afrique du Sud === {| style="border-spacing:0;" | style="border-top:0.05pt solid #000000;border-bottom:0.05pt solid #000000;border-left:0.05pt solid #000000;border-right:none;padding:0.097cm;"| [[Image:Ict-innovation-FBT-Fig-2-4.png]] | style="border:0.05pt solid #000000;padding:0.097cm;"| Raison sociale : Linux Holding (Pty) Ltd. Fondée en : 2003 Effectif : 9 Pays : Afrique du Sud Site Web : [http://www.linuxholdings.co.za/ http://www.linuxholdings.co.za] Type d’entreprise : Formation Linux |} ==== Résumé ==== Linux Holdings est une entreprise basée en Afrique du Sud se focalisant principalement sur le développement et l’organisation de formations Linux destinées aux particuliers et aux entreprises libres et open source intéressées en Afrique du Sud et dans les pays limitrophes. Fondée en 2003 et avec 9 employés, Linux Holdings vise à être un leader dans l’environnement éducatif open source. ==== Introduction ==== Le personnel de base de Linux Holdings Ltd se compose d’enseignants qui utilisent fréquemment les résultats des sondages auprès de leurs clients pour déterminer les besoins en formation du marché des logiciels libres et open source. Ils conçoivent les programmes de formation et produisent les outils personnalisés pour les clients de l'entreprise. En tant que tentative de programme scolaire, le Président-directeur général a résumé le profil de l'entreprise comme étant composé d’un personnel doté d’une expérience collective en matière de conception de programmes et de formations. L’entreprise développe ses propres outils de formation, ce qui a grandement contribué à la “réussite de nos étudiants”, a déclaré le directeur. En ce qui concerne Linux Holdings, la motivation pour s’impliquer dans la formation Linux comme un objectif majeur correspond à la motivation, l’intérêt et l’expérience du directeur dans le domaine du logiciel libre et de l’open source; <nowiki>“C’est un domaine [open source] que j’ai aimé et que je connais. Il est inutile de faire des affaires dans un domaine que vous détestez ou qui ne vous dit pas grand-chose. Un secret de la réussite dans les affaires consiste à avoir du plaisir dans ce que vous faites et qui vous passionne. De cette façon votre chance de réussir augmente”.</nowiki> ==== La vision d’entreprises en logiciels libres et open source ==== Linux Holdings est le résultat de la combinaison de l’expertise et de l’expérience à divers niveaux. Le cœur d'activité de l'entreprise est basé sur la formation Linux. La société offre également d’autres services open source en marketing, hébergement de serveurs et en développement de logiciels. Au cours des dernières années, Linux Holdings a capitalisé de l’expérience en matière de mobilisation de capital pour le démarrage, l'obtention de crédits pour financer les initiatives d’entreprise en Afrique du Sud, le développement d’une base de clientèle, le recrutement de personnel, l'acquisition de locaux pour les bureaux, l'obtention des documents de société, etc. Les aspects suivants démontrent l’expérience de Linux Holdings: * Un bon plan stratégique est mis en place, avec la possibilité de l’étendre et d’accommoder des changements imprévus dans les conditions du marché. Disposez d’un plan de 3 ans et de 5 ans avec des stratégies pour des échéances plus courtes. * Fixer des objectifs réalistes et réalisables. Commencer petit et puis se concentrer sur une extension ordonnée du capital * Avant de démarrer les affaires, identifier ce qui est nécessaire et souhaité dans l’industrie ou marché cible. Parler aux gens, faire des enquêtes et savoir ce que font vos concurrents. Ensuite prendre ce qui est nécessaire et demandé et le livrer comme produit. * Être bien informé au sujet du monde des affaires et connaître les lois régissant les affaires dans le pays. Beaucoup de problèmes rencontrés dans les affaires sont dus à un manque de connaissance préalable des lois qui régissent un domaine d’activité particulier. En outre, le chef de la compagnie résume ainsi comment le manque de connaissance préalable peut conduire à des défaillances d’entreprise: “les problèmes financiers proviennent de la méconnaissance de planification financière. Ne pas connaître son produit est une cause d’échec de sa commercialisation.. Ne pas comprendre le contrôle de qualité, de marketing et de RP crée des problèmes dans ces domaines”. Prenant en compte ces expériences, les conseils du directeur de Linux Holdings pour quelqu’un qui développe une entreprise autour de logiciels libres et open source en Afrique en général, et en Afrique du Sud en particulier sont: * la nécessité de comprendre les lois du pays concernant l'entreprise, * travailler avec les logiciels libres et open source et apprendre les avantages offerts par ces solutions, * s’intéresser à ce que font vos concurrents potentiels, apprendre d’eux et envisager d’autres services correspondant à des besoins pour les clients. * si vous êtes en mesure d’offrir des services de qualité supérieure à ceux de vos concurrents, alors vous réussirez dans les affaires. «La qualité est toujours la partie la plus importante d’une activité”, a dit Kin Le Roux, Président-directeur général de Linux ==== Le modèle pour générer des revenus ==== La formation compte pour 80% des revenus. Linux Holdings fait face à un défi unique en matière de formation d’une génération d’Utilisateurs de Linux en Afrique du Sud. L’association du mot «libre» avec le logiciel libre open source doit être gratuite également ou tout au moins, très bon marché. Linux Holdings a fait remarquer que cette façon de penser ne tient pas compte des coûts et des ressources (Humaines et Matérielles) que la société de formation supporte, ce qui peut être le même ou parfois plus élevés que d’autres cours de formation en informatique offerts par d’autres entreprises de formation. Linux Holdings a une politique ferme qui consiste à être payé avant la prestation de services ou avant l’immatriculation des étudiants et l’achèvement de leurs cours. «Le département finances dans Linux Holdings maintient agressivement (pas d’une façon impolie ou menaçante) aussi basses que possible les encours des clients» a déclaré le Président-directeur général. Le département marketing, en revanche, est responsable de la création et de l’amélioration des outils de formation pour Linux Holdings. Toute la formation est faite en anglais. La société a environ 20 étudiants inscrits par session dans son programme académique. La formation d’entreprise reçoit environ 5 à 8 étudiants par cours. Tous les étudiants doivent être informés avant le démarrage de la formation qu’ils doivent payer avant le 5 du mois, autrement ils ne seront pas admis dans la salle de cours. ==== Travail en réseau ==== La société a une façon unique de faire le marketing. Par le biais d’enquêtes, le département marketing identifie ce qui est attendu et souhaité et ensuite à partir des résultats, la compagnie crée le produit correspondant à ce besoin. Parfois cela se fait via le site Web de la société et en téléphonant aux clients actuels. === e) L’Expérience de Linux Solutions === {| style="border-spacing:0;" | style="border-top:0.05pt solid #000000;border-bottom:0.05pt solid #000000;border-left:0.05pt solid #000000;border-right:none;padding:0.097cm;"| [[Image:Ict-innovation-FBT-Fig-2-5.png]] | style="border:0.05pt solid #000000;padding:0.097cm;"| Raison sociale : Linux Solutions Ltd. Fondée en : 2000 Effectif : 10 Pays : Ouganda Site Web : [http://www.linuxsolutions.co.ug/ http:www.linuxsolutions.co.ug] Type d’Entreprise : Ventes de Logiciel et de Matériel informatique |} ==== Résumé ==== Linux Solutions a été fondée en 2000 et emploie actuellement 10 personnes. L’entreprise a démarré comme une société open source pure mais en raison de la nature du marché de logiciels en Ouganda, où les clients sont habitués aux logiciels propriétaires, Linux Solutions considère qu’il est bon d’intégrer d’autres services et produits comme un moyen de rester compétitif et assurer la survie de l’industrie. La société offre une large gamme de services et de produits y compris l’assistance technique, le conseil, le déploiement de logiciels, la fourniture de matériel informatique IT et le travail en réseau. ==== Introduction ==== Linux Solutions Ltd est engagé dans la promotion et dans le business de logiciels libres et open source depuis plus de huit ans. La motivation de l’entreprise à choisir l’open source comme une activité principale relève de trois piliers principaux: * Fournir un service dans le but d’encourager l’adoption des logiciels open source en Ouganda * Prouver au monde des affaires que l’open source fonctionne et qu’il est rentable. * Se tailler une place spécifique qui donnerait à la société un avantage concurrentiel. Quand on lui a posé la question de savoir jusqu’où Linux Solutions est allée dans la réalisation des orientations ci-dessus, le Président-directeur général a déclaré: “Nous avons enregistré des succès en tout. En 2008, il y avait 15 sociétés qui offraient des services Linux dans le marché de logiciels libres et open source ougandais. De nombreuses d’entreprises ont choisi open source par la fourniture de serveurs. Ainsi, l’Ouganda est un pays qui a une très haute concentration de serveurs informatiques s’appuyant sur l’open source. Aujourd'hui, l’assistance technique pour les outils libres et open source est aussi facile à trouver que celle proposée pour Windows. Il y a un Groupe d’Utilisateurs de Linux comptant plus 300 membres locaux ougandais dont la plupart sont employés dans des entreprises open source”. ==== La vision d'entreprise en logiciels libres et open source ==== En raison de son expérience de longue date et de son implication dans les logiciels libres et open source en Ouganda, Linux Solutions a rencontré nombre de problèmes et a employé des stratégies pour y faire face. L’entreprise a identifié les éléments suivants comme obstacles possibles à l’encontre de ceux qui veulent faire des affaires autour du logiciel libre et open source en Ouganda: * L’obtention de capital de démarrage d’entreprise et le financement de nouveaux projets ou d'initiatives. * L’approvisionnement en produits internationaux. * La gestion du personnel et la comptabilité. * La gestion et la rétention des ressources humaines. * De mauvaises habitudes de paiement par les clients. * La qualification pour répondre aux critères des contrats gouvernementaux. En abordant certains de ces problèmes, Linux Solutions a dû démarrer comme une petite entreprise avec quelques individus et avec peu d’argent et de ressources à leur disposition. Progressivement, l’entreprise s’est appuyée sur des revenus entrants pour faire croître l’entreprise. Bien que les contrats gouvernementaux soient lucratifs, Linux Solutions préfère les clients du secteur privé par opposition aux contrats gouvernementaux. Cette démarche peut être due au fait que les clients du secteur privé sont souvent des petites institutions, plus clairement focalisées sur leurs besoins, moins bureaucratiques, et souvent paient leurs clients plus rapidement que les ministères gouvernementaux. Linux Solutions emploie un expert comptable à plein temps et un consultant financier externe sous contrat pour conseiller dans les questions financières et comptables de la société. En traitant avec ses clients, Linux Solutions met en place des modalités de paiement qui sont strictement appliquées et discutées avec les clients avant de s’engager dans toute forme de contrat. Concurrencer ou coopérer: Fondée en 2000 lorsqu’aucune entreprise n’envisageait de faire des affaires autour de logiciels libres et open source en Ouganda, Linux Solutions peut être décrite comme 'le grand-père' de toutes les compagnies open source en Ouganda. Quand les nouveaux acteurs ou des PME basées sur open source débarquent sur le marché, Linux Solutions leur tend la main, simplifiant et expliquant aux nouveaux venus comment traiter avec des clients potentiels. Pour sa part, la société éduque ses clients autant que possible au sujet du logiciel qu’elle déploie et maintient. Cette approche, selon le Président-directeur général, “fait qu’ils (les clients) nous accordent plus d’importance”. Un autre aspect qui a fonctionné correctement pour l’entreprise dans ses activités est le fait qu’ils sont toujours disponibles pour offrir une assistance lorsque c’est nécessaire. La société déploie beaucoup d’effort pour assurer qu’elle est excellente quant à la promptitude en matière de prestation de services. L’entreprise a localisé le navigateur Web Firefox il y a quelques années comme une activité pro bono. Cependant, la société n’est plus impliquée dans toute localisation de logiciels, car, comme l’a dit le Directeur, il n’y a “plus d’argent à gagner”. Linux Solution commercialise ses produits et services par le biais de marketing direct, groupe de ciblage marketing, les sections classifiées de la presse écrite, son propre site Web, par courrier électronique à quelques agents connus et les groupes d’intérêt, et par les lancements de produits auxquels le public est invité à regarder les démos. Les plans d’affaires de Linux Solution pour les quelques prochaines années visent à améliorer sa position concurrentielle et incarne les éléments suivants: * Améliorer la gestion du service client. * Disposer d’une main-d’œuvre technique hautement qualifiée qui a également des compétences en matière de ventes et de service à la clientèle * Améliorer l’image de marque de l’entreprise. * Adapter le marketing pour répondre aux types de clients cibles de la société. * Répondre aux besoins des personnes et réduire l’accent sur la technologie dans la communication avec les clients. ==== Services ==== Pour une partie de ses activités principales, Linux Solutions fournit et soutient les technologies fournies par [http://www.ncomputing.com/ www.ncomputing.com], [http://www.inveneo.org/ www.inveneo.org], et des logiciels antivirus de [http://www.kaspersky.com/ www.kaspersky.com] Tous les services (Soutien Technique, Conseil, Déploiement de logiciels, Fourniture de matériel informatique IT, et Travail en réseau) de Linux Solutions sont un mélange de logiciels open source et propriétaires. C’est parce qu’un nombre des clients de la société demande des services sur les logiciels open source et propriétaires. Parmi les services, le déploiement de logiciels et la fourniture de matériel informatique IT génèrent plus de revenus pour Linux Solutions. Dans le contexte de l’entreprise, le déploiement de logiciels signifie vendre les logiciels anti-virus et anti-spam, l’installation de serveurs Firewall/MailFile/DNS/Proxy (sur Linux) parmi d’autres. Linux Solutions fait plus de personnalisation de solutions existantes open source que de développement de nouvelles solutions. Certains des logiciels open source que la société a personnalisés comprennent Squid, Webmail, (SquirreMail), Webmin, OpenVPN, etc. La personnalisation n’est pas habituellement au niveau du code. La société a trois développeurs expérimentés dans l’open source dont la principale responsabilité est de mettre au point les interfaces, et les options de déploiement pour faire faire certains travaux pour leurs clients. Toute amélioration ou modification que l’entreprise fait sur les logiciels open source est répartie sur les listes de diffusion. ==== Leçons retenues ==== Suite à cette expérience, le conseil de Linux Solutions à quelqu’un qui veut fonder une entreprise autour de logiciels open source en Afrique en général et en Ouganda en particulier est d’éviter de se spécialiser sur un seul secteur de service ou de produit. Par exemple, démarrer une entreprise dans laquelle on vendra des ordinateurs avec Ubuntu préinstallé, ou créant un centre de formation pour enseigner les cours Linux. Selon le président-directeur général, il y a une nécessité de diversifier afin de s’assurer qu’au fur et à mesure qu'une source de revenus diminue dans un secteur de services, une autre vous renfloue probablement. Dans ce marché, les PME doivent avoir d’autres services ou produits qui complètent leur opportunité focalisée sur l’open source afin de survivre. === f) L’Expérience d’Amest Santim Systems PLC === {| style="border-spacing:0;" | style="border-top:0.05pt solid #000000;border-bottom:0.05pt solid #000000;border-left:0.05pt solid #000000;border-right:none;padding:0.097cm;"| [[Image:Ict-innovation-FBT-Fig-2-6.png]] | style="border:0.05pt solid #000000;padding:0.097cm;"| Raison sociale : Amest Santim Systems PLC Ltd. Fondée en : 2005 Effectif : 5 Pays : Éthiopie Site Web : [http://www.amestsantim.com/ http://www.amestsantim.com] Type d’entreprise : Formation en logiciels libres et open source, développement de logiciels, hébergement de sites Web |} ==== Résumé ==== Amest Santim Systems plc est une entreprise éthiopienne de développement de logiciels et d’hébergement de sites Web qui offre un éventail de services y compris dans le domaine enregistrement de nom, le travail en réseau, la maintenance, le Conseil TIC, externalisation, etc. Actuellement la société a cinq employés à plein temps et réalise 90% de son chiffre d’affaires du développement de sites Web, 7% d’hébergement de sites Web et 3% d’autres services. ==== Introduction ==== <nowiki>En 2005, deux spécialistes éthiopiens en TIC, armés d’un capital de départ d’environ $ 200 US et quelques ordinateurs à domicile se sont réunis pour former Amest Santim Systems plc. Pour ses fondateurs, la plus grande motivation pour s’impliquer dans le business open source est l’idée “de ne pas réinventer la roue”. La plupart des outils open source dont les fondateurs avaient besoin pour le développement de logiciels et les sites Web sont disponibles gratuitement pour téléchargement, utilisation et modification. Le président-directeur général, Nahom Tamerat Endale, a fait remarquer que ces outils “peuvent être complètement fiables”. La culture de passion pour l’open source dans beaucoup de cas incite les entreprises à se concentrer sur l’amélioration des outils et des logiciels existants au lieu de créer de nouveaux projets. Mais pour Amest Santim Systems plc., l’idée de “ne pas réinventer la roue a ajouté des avantages en faveur de la compagnie. “Ainsi à la fin, l’effort que vous aurez à déployer dans ce projet serait 1/10 de ce que vous auriez à donner si vous deviez développer [l’outil] vous-même et par conséquent, nous choisissons [les outils open source] chaque fois que c’est applicable”.</nowiki> L’Éthiopie est l’un des pays dotés de collèges privés et le gouvernement a également augmenté de près de dix fois le nombre de ses universités, et en conséquence, il y a de nombreux diplômés universitaires en TIC. L’approche des deux fondateurs d’Amest Santim Systems au sujet du capital de démarrage était de faire en quelque sorte que l’entreprise s’autofinance. Au début, les deux fondateurs, actuellement directeur général et l’autre directeur technique se sont servis de leurs ordinateurs pour travailler sur les projets à partir de leur propre domicile. Après les six premiers mois, ils ont utilisé l’argent qu’ils ont gagné pendant cette période pour louer et meubler ce qui est présentement le complexe de bureaux de la société. Par ailleurs, les deux fondateurs ont apporté leurs propres ordinateurs de la maison. Un an plus tard, toujours avec l’argent qu’ils ont gagné, ils ont acheté de nouveaux ordinateurs et ont embauché de nouveaux employés et ainsi de suite. Réfléchissant sur cette expérience, le président-directeur général a fait remarquer… «Alors nous pouvons dire que nous avons commencé avec rien du tout. En effet, le chiffre exact était 2000 ETB (approximativement $ 200 US pour les démarches en vue de l’obtention de la licence et d’autres coûts que nous avons encourus lorsque nous avons lancé l’entreprise». ==== La vision d’entreprises en logiciels libres et open source ==== Le conseil du président-directeur général d’Amest Santim Systems plc. à une personne désirant créer une entreprise autour de logiciels open source en Afrique en général et en Éthiopie en particulier est: * Rendez les prix de vos produits et services abordables * Assurez-vous que tout ce que vous proposez corresponde à l’objet et que le client va vraiment l’utiliser. * Personnalisez les logiciels autant que vous pouvez, impliquant vos clients dans le processus. * Soyez honnête au sujet de votre produit, ce qu’il peut faire et ce qu’il ne peut pas faire. Ne promettez pas ce que vous ne pouvez pas livrer ou ce que vous ne pouvez pas faire ou ce que le logiciel ne peut pas faire. * N’essayez toujours pas de vendre aux clients les logiciels que vous avez reçus gratuitement même lorsque vous les avez personnalisés...soyez créatifs pour trouver les moyens de mériter votre salaire. Essayez de gagner des revenus de la maintenance de logiciels, entre autres. * Soyez créatif! Soyez flexible! ==== Leçons tirées ==== Amest Santim Systems plc. a appris auprès d’autres sociétés impliquées dans d’autres activités en Éthiopie et dans les pays limitrophes à surmonter des défis uniques associés aux business menées dans l’open source et a vu son chiffre d’affaires monter en flèche en ce moment de récession économique. Il y a beaucoup à apprendre des stratégies employées par l’entreprise pour attirer et construire une clientèle durable, embaucher un personnel compétent, exploitant ses propres revenus comme capital de démarrage. ==== Barrières à l’entrée ==== L’entreprise a rencontré une grande diversité de défis et de problèmes. D’abord et avant tout on note le défi de « construire une base de clientèle». Déjà, Il y avait quelques firmes TIC bien connues et installées en Éthiopie lorsque l’entreprise a démarré en 2005. Ces sociétés avaient travaillé pour de nombreux grands clients (tels que les organes du gouvernement et les ONG et, par conséquent, ils avaient déjà une marge de manœuvre auprès d’eux. Ainsi, tout nouveau venu pourrait avoir à surmonter d’énormes défis et rivaliser avec l’environnement d’affaires établi. Mais Amest Santim Systems a persévéré, a étudié le marché TIC et a proposé quelques solutions possibles : * réduire les prix de leurs produits et services et même dans un cas, l’entreprise a fait le travail gratuitement * participer audacieusement à des soumissions publiques pour des projets, présentant la société professionnellement (ce qui est quelque chose que la plupart des autres ne semblent pas être capables de faire…) etc. Une fois que la compagnie avait exécuté quelques projets prestigieux, les clients ont commencé à venir à Amest Santim Systems plc. au lieu que la société aille solliciter du travail auprès des clients. Un autre problème auquel la société a fait face était (et qui dans une large mesure, existe toujours) le manque de main-d’œuvre qualifiée. ==== Conclusion ==== Même si Amest Santim Systems plc a pu suivre ces conseils dans son fonctionnement et en traitant avec les clients, faire des affaires n’est pas sans difficultés, surtout quand il s’agit de la recherche de clients, de la formation, de l’embauche du personnel et de se faire payer pour les services que la compagnie offre. === g) CENFOSS - Utilisant les logiciels libres et open source pour les affaires === {| style="border-spacing:0;" | style="border-top:0.05pt solid #000000;border-bottom:0.05pt solid #000000;border-left:0.05pt solid #000000;border-right:none;padding:0.097cm;"| [[Image:Ict-innovation-FBT-Fig-2-7.png]] | style="border:0.05pt solid #000000;padding:0.097cm;"| Raison sociale : CENFOSS Ltd. Fondée en : 2006 Effectif : 12 Pays : Mozambique Site Web : [http://www.cenfoss.co.mz/ http://www.cenfoss.co.mz] Type d’Entreprise : Formation en logiciels libres et open source, développement de logiciels, hébergement de sites Web |} ==== Introduction ==== CENFOSS – CENTRO DE FORMACAO EM OPEN SOURCE SOFTWARE a été fondé en 2006 à Maputo comme le premier Centre officiel de Formation en Logiciels libres et open source au Mozambique. Au milieu de 2007, la société a commencé à offrir des services dans le domaine d’Hébergement de sites Web, Conception de sites Web et Conseil en logiciels libres et open source. L’idée de CENFOSS a commencé après l’atelier en Mozambique en 2006. L’utilisation de logiciels libres et open source n’était pas une nouveauté au Mozambique. Mais jusqu’à cette date, aucun des centres de formation, aucune des écoles ou des universités ne considérait le logiciel libre et open source comme une entreprise de formation et de services. Moi, Celso, en compagnie d’autres anciens d’InWent, Ricardo Mario Taca, avons décidé de développer des outils de formation pour les cours. Mais il fallait un local et du matériel informatique pour faire les cours. A ce moment-là aucun d’entre nous, les anciens, n’avait assez d’argent pour le débuter». CENFOSS a été enregistré avec 4 partenaires, Celso Timana, Eduardo Timana, Ana Soares et Olga Reina, un seul partenaire avec l’expérience en logiciels libres et open source. Le reste des partenaires avait accordé un soutien logistique à CENFOSS. Nous avons démarré les cours en logiciels libres et open source avec deux formateurs, un à plein temps et un autre à temps partiel. De septembre 2006 jusqu’à février 2007 CENFOSS avait 3 employés. Maintenant, deux des fondateurs ont quitté CENFOSS pour permettre à deux autres d’intégrer CENFOSS: Orvalho Augusto et Rui Reina. Et depuis lors les effectifs du personnel a augmenté jusqu’à 12 employés dont: * Deux sont des partenaires CENFOSS * Trois secrétaires qui se relayaient * Cinq techniciens * Deux assistants de bureau Puisque nous avons plus de 10 employés au Mozambique, cela veut dire que nous sommes une entreprise moyenne. ==== Quels services ou quelles solutions logicielles sont offertes? ==== L’une des principales offres que nous avons dans nos services a été la configuration de serveur courrier. Actuellement, plus d’entreprises, de petites et moyennes entreprises, sont de plus en plus conscients des avantages d’avoir leur marque comme nom de domaine. Sans l'utilisation du logiciel libre et open source comme outil principal, il ne nous serait pas possible d’atteindre nos objectifs. Nous offrons le suivi de réseau à des sociétés qui veulent contrôler leur réseau par rapport à la consommation de bande passante. Nos services basés sur les logiciels libres et open source sont: * Serveur de messagerie – relais de courrier électronique/filtrage/, et webmail (Clamav, Amavis, Postfix, SpamAssassin, Dovecot, Squirrelmail and Zimbra) * Web Proxy – Squid * Modélisation de base de données – MySQL, PostgreSQL * Serveur Web – Apache, lighttpd * Système de Gestion de Contenu – Joomla, Drupal * Conseil Statistique * Conception de Logiciels d’application – Utilisant les langages de programmation tels que: ** Python ** Java ** PHP5 ** Perl Outre les avantages TCO, nous avons l’occasion de montrer à nos clients qu’il est possible de faire des affaires et d’avoir une entreprise qui utilise le modèle de logiciels libres et open source. Nos activités nous ont donné l’occasion de collaborer avec d’autres communautés autour de logiciels libres et open source. ==== Lien avec d’autres sociétés, agences, ONG et le gouvernement ==== CENFOSS a conclu des accords de coopération et des contrats avec plusieurs institutions allant du public au privé et les ONG. * CodeWeavers – Est une entreprise basée aux USA, qui développe l’outil open source Wine pour l’interopérabilité entre Windows et Linux et CrossOver Linux – un outil commercial open source ayant le même objet que Wine. CENFOSS est un client de Codeweavers. * Le Ministère des Affaires Étrangères – CENFOSS a mis en œuvre et s’occupe de la maintenance du serveur de messagerie de ce ministère. * l’Université Eduardo Mondlane, Branche des Mathématiques et d’Informatique - CENFOSS a conclu un accord de partenariat avec cette université pour la formation des professeurs. Jusqu’à présent, nous avons organisé un atelier sur les Affaires Libres open source avec 60 étudiants en 2008. * SOCREMO – Banque de Micro finances – actuellement CENFOSS soutient GNU/Linux sur SOCREMO à l’aide d’un contrat d'assistance d’un an. Le travail implique plus de 150 ordinateurs d’appui et également 3 serveurs tournant sur GNU/Linux. * CEDE, AMODE, RRD, ZE Servicos - CENFOSS offre les services d’hébergement de sites Web et de courriel à ces sociétés et organisations. ==== Qui sont nos cibles ? ==== Les clients de CENFOSS sont classés selon les services que nous offrons. Des étudiants d’université sont ceux qui ont le plus besoin de nos services de formation. Au début de 2008, nous avons commencé à offrir les services de conseil en matière de logiciels libres et open source. Depuis lors nous assistons des compagnies de télécommunications, des banques privées et les institutions gouvernementales. Le tableau ci-dessous dresse une liste succincte de nos clients actuels : {| class="wikitable" ! Service Offert par CENFOSS ! Les clients ! Type d’entreprise & Service ! Taille de la société |- | Formation en MySQL et Unix/FreeBSD | VODACOM Mozambique | Fournisseur priveé GSM | Grande Enterprise |- | Formation et mise en œuvre des technologies pour les serveurs de Nom de domaine national | Télécommunication de Mozambique (TDM) | Etablissement public pour les lignes de communication fixes et ISP (Fournisseur de services Internet) | Grande Enterprise |- | Configuration serveur de courrier électronique et formation Linux | Ministère des Affaires Etrangères | Etablissement Public | - |- | Formation en CMS | Ministère de l’Intérieur | Etablissement Public | - |- | Formation en Linux | Ministry of Commerce | Etablissement Public | - |- | Formation en MySQL | Banque Commerciale d’Investissements – BCI | Banque Privée | Grande Enterprise |- | SoutienLinux , formation | SOCREMO | Banque Privée | Grande |- | Formation en Linux | GSTELECOM | ISP Privé | Grande |- | Formation en Linux | ISP Privé | Moyenne | |- | Formation en Linux | ISP Privé | Moyenne | |- | Hébergement Web et courriel | CEDE | ONG | Moyenne |- | Hébergement Web et courriel | AMODE | ONG | Moyenne |- | Hébergement Web et courriel | RRD | Société de Conception | Petite |- | Hébergement Web et courriel | Institut National de Navigation | Etablissement Public | Grande |- | Hébergement Web et courriel | Ze Servicos | Firme Privée | Petite |} ==== Leçons retenues ==== Après avoir travaillé pendant presque trois ans avec les logiciels libres et open source nous avons acquis assez d’expérience au fil du temps. Nous avons eu à faire face à des situations où le logiciel libre et open source tout seul n’était pas la solution. Puisque nous soutenons de nombreuses sociétés qui font du business avec les logiciels libres et open source et qui utilisent les logiciels libres et open source, nous continuerons à accumuler de l’expérience et nous sommes mieux placés pour donner des conseils aux gens, aux entreprises et aux organisations au sujet de quelle technologie utiliser dans leur entreprise, mais aussi où et quand utiliser cette technologie. ==== Conclusion ==== CENFOSS s’est positionné sur le marché comme fournisseur de formation et de solutions basées sur les logiciels libres et open source. Jusqu’ici nous sommes le seul centre de formation entièrement dédié aux logiciels libres et open source au Mozambique. Cela ne nous donne aucun avantage sur les nouveaux arrivants, qui pourraient également être nos concurrents potentiels. Poursuivre rigoureusement une campagne de marketing, tisser des liens solides avec les clients anciens ainsi qu’avec les nouveaux clients constituent quelques-uns de nos objectifs pour les années à venir. [[Ict@innovation:_Free_your_IT_Business_in_Africa/fr/2-Assignments|Chapitre précédent]] | [[Ict@innovation:_Free_your_IT_Business_in_Africa/fr/2-Test|Chapitre prochain]] [[Catégorie:Ict@innovation: Free your IT Business in Africa]] [[en:Ict@innovation: Free your IT Business in Africa/2-Others]] 5m8r26jnst0o4bufc3tavhtsxa0scj3 Wikijunior/Le mouflon 0 65542 768583 515374 2026-06-25T09:05:12Z Xhungab 23827 768583 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis : 7 avril 2016 à 17:14 38.104.158.170. Sans contenu pertinent}} Le mouflon est un mammifère des montagnes. Il peut vivre 20 ans et mesurer 2 m de long. {{AutoCat}} 8dr8xjgxvauqo6dl3m6gs37v7r01e3q Wikijunior/Le renard roux 0 65543 768584 515375 2026-06-25T09:05:57Z Xhungab 23827 768584 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis : 7 avril 2016 à 17:14 38.104.158.170. Sans contenu pertinent}} Le renard roux est un mammifère de forêt. Il peut vivre 12 ans, et peut mesurer un mètre de long. {{AutoCat}} kmj3ngr9ac471791lfej7suik6qsso1 Fonctionnement d'un ordinateur/Le chemin de données 0 69025 768547 765265 2026-06-24T21:21:10Z Mewtow 31375 /* L'interface de l'unité mémoire */ 768547 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 0 à 3 : T0, T1, T2, T3. 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. {|class="wikitable" |- ! T0 !! T1 !! T2 !! T3 |- | 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" |- ! T0 !! T1 !! T2 !! T3 |- | Adresse || colspan="3 | Donnée à écrire |} ==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> n78iq4ltqz1dqex1suximke5tfzwwjm 768548 768547 2026-06-24T21:29:16Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768548 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 0 à 3 : T0, T1, T2, T3. 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" |- ! T0 !! T1 !! T2 !! T3 |- | 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" |- ! T0 !! T1 !! T2 !! T3 |- | Adresse || colspan="3 | Donnée à écrire |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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. 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 0. 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. ==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> 8tibpdpr5z1ba6zk0md8ooozwx1p90y 768549 768548 2026-06-24T21:35:39Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768549 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 0 à 3 : T0, T1, T2, T3. 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" |- ! T0 !! T1 !! T2 !! T3 |- | 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" |- ! T0 !! T1 !! T2 !! T3 |- | Adresse || colspan="3 | Donnée à écrire |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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 0. 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. ==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> ogiujvpr8ij2ryoagftj6uz8src0cq5 768550 768549 2026-06-24T21:38:23Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768550 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 0 à 3 : T0, T1, T2, T3. 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" |- ! T0 !! T1 !! T2 !! T3 |- | 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" |- ! T0 !! T1 !! T2 !! T3 |- | Adresse || colspan="3 | Donnée à écrire |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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 0. 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. La première bascule est à 1 pour le cycle T0, la seconde est à 1 pour le cycle T1, la troisième est à 1 pour le cycle T2, la quatrième l'est pour le cycle T3. 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. ==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> 8d6764hjiwdket8xkt1bpollmjx5f2c 768551 768550 2026-06-24T21:49:02Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768551 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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. 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. 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. {|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 |} ==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> jx0eyn7ckb4kgu0190nppa9d1royd8j 768552 768551 2026-06-24T21:55:42Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768552 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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. 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. 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 |} Un dernier détail à aborder est que 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 au niveau de la "machine à état" était 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. ==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> 7vo523321kij0wc7vcs3bka4kxfli2d 768553 768552 2026-06-24T21:57:53Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768553 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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. 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. 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 |} Un dernier détail à aborder est que 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 au niveau de la "machine à état" était 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. : 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> 7ityxlgypy2uf4bkkqiwpv8hx7gpqwj 768554 768553 2026-06-24T22:12:29Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768554 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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. 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. 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 au niveau de la "machine à état" était 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. [[File:Machine à état de l'unité mémoire du 8086 avec son compateur one hot.png|thumb|Machine à état de l'unité mémoire du 8086 avec son compateur one hot]] pour résumer, l'unité mémoire du 8086 utilise une machine à état connectée aux registres d’interfaçage mémoire. La machine à état contient un compteur ''one hot'', composé de quatre bascules. 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''. 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> 8pakph3yaqk7c6sxittques0vwxa995 768555 768554 2026-06-24T22:12:49Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768555 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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 |} Le registre d’interfaçage pour les adresses est donc 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. Il y a donc un circuit qui détermine quand connecter ces deux registres sur le bus mémoire. 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. 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. 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 au niveau de la "machine à état" était 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. pour résumer, l'unité mémoire du 8086 utilise une machine à état connectée aux registres d’interfaçage mémoire. La machine à état contient un compteur ''one hot'', composé de quatre bascules. 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> nd59zvoc4p45st43g9jvhwwlzr6w4ye 768556 768555 2026-06-24T22:16:42Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768556 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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. 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> qmlphpap84r7ml1w7i5h65l4clnbq0g 768557 768556 2026-06-24T22:43:42Z Mewtow 31375 /* Un exemple : l'unité mémoire du 8086 et ses dérivés */ 768557 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 parfois être adressables, encore que c'était surtout le cas 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. L'AGU est utilisée pour implémenter 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 les indices dans certains cas, 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. Rappelons que dans ce chapitre, ainsi que les quatre 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. Les processeurs à registres généraux, donc. Ils peuvent intégrer une AGU, mais c'est très rare. L'ALU entière suffit largement pour implémenter les modes d'adressage complexes. 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> 489pp3bykrsphrhhlojnkevlv55dpex Fonctionnement d'un ordinateur/Le modèle mémoire : alignement et boutisme 0 69028 768546 765320 2026-06-24T20:19:53Z Mewtow 31375 /* Les architectures avec une mémoire adressable par mot */ 768546 wikitext text/x-wiki Dans ce chapitre, on va parler de l'''endianess'' du processeur et de son alignement mémoire. Concrètement, on va s'intéresser à la façon dont le processeur repartit en mémoire les octets des données qu'il manipule. Ces deux paramètres sont sûrement déjà connus de ceux qui ont une expérience de la programmation assez conséquente. Les autres apprendront ce que c'est dans ce chapitre. Pour simplifier, ils sont à prendre en compte quand on échange des données entre registres et mémoire RAM. ==La différence entre mots et bytes== Avant toute chose, nous allons reparler rapidement de la différence entre un byte et un mot. Les deux termes sont généralement polysémiques, avec plusieurs sens. Aussi, définir ce qu'est un mot est assez compliqué. Voyons les différents sens de ce terme, chacun étant utile dans un contexte particulier. La distinction entre un octet, un byte et un mot, se fait au niveau du processeur. Précisément, elle intervient au niveau des instructions d'accès mémoire, éventuellement de certaines opérations de traitement de données. Dans ce qui va suivre, nous allons faire la différence entre les architectures à mot, à byte, et à chaines de caractères. ===Les mots et ''bytes'' : entiers et caractères=== Vous savez sans doute qu'il existe des processeurs 8, 16, 32 ou 64 bits, ce nombre indiquant le nombre de bits utilisés pour coder un nombre entier. Et bien un '''mot''' est une donnée de taille fixe, qui a la même taille. En clair, un mot est toute donnée qui a la même taille qu'un entier : ça peut être un entier, une adresse mémoire, des données bit à bit, ou toute autre donnée. Précisons qu'il s'agit de la taille maximale, précision qui est importante car certains processeurs gèrent des entiers de taille distinctes. Par exemple, un processeur 32 bits peut avoir des instructions de calcul 16 ou 8 bit. Dans ce cas, c'est la taille maximale qui compte. La taille des nombres entiers se répercute sur beaucoup d'aspects du jeu d'instruction, notamment la taille des registres. Un processeur 32 bits utilise des entiers codés sur 32 bits, et il a donc des registres entiers de 32 bits. Précisons qu'il s'agit là uniquement des registres entiers, les registres flottants peuvent être plus larges. Un '''''byte''''' est une unité plus petite que le mot, typiquement un octet, initialement utilisée pour faciliter l'encodage du texte. Un ''byte'' correspondait dans le temps à un caractère de texte, codé en ASCII ou un autre codage similaire. La norme actuelle est de 8 bits par ''byte'', mais ça n'a pas toujours été le cas. Elle s'est instaurée au début des années 1970, quand les processeurs ont commencés à s'adapter au traitement du texte. Avant, les processeurs utilisaient des ''bytes de 6, 7, 9 ou 10 bits. Pour bien faire les choses, un mot contient un nombre entier de bytes, pas un bit de plus ou de moins. Le nombre d'octets dans un mot est généralement une puissance de deux pour simplifier les calculs. Cette règle souffre évidemment d'exceptions, qui posent quelques problèmes techniques en termes d’adressage, comme on le verra plus bas. Sur les machines modernes, chaque ''byte'' a une adresse, ce qui fait que le processeur peut lire un ''byte'' indépendamment des autres. Le ''byte'' est souvent décrit comme la plus petite unité de mémoire que le processeur peut adresser. Mais quelques architectures très rares ne respectent pas cette règle, en permettant d'adresser carrément chaque bit de la mémoire indépendamment des autres. Voyons rapidement la distinction entre mot et ''byte'' sur les processeurs 8, 16, 32 bits et plus. La distinction entre mot et ''byte'' est la plus simple sur les processeurs 16 bits : le mot fait 16 bits, le ''byte'' en fait 8, un mot est donc composé de deux ''bytes''. Les processeurs 8 bits récents ne gèrent que des données de 8 bits, ce qui fait que le mot et le ''byte'' sont confondus. Le processeur manipule des mots qui sont de la même taille que le ''byte''. Il a existé quelques processeurs capables de manipuler des données de 4 bits, notamment pour supporter le BCD, mais cela ne concernait que les instructions. Les registres et la mémoire utilisaient des ''bytes'' d'un octet. Sur les processeurs 32 bits, le ''byte'' fait un octet, le mot en fait 4, le processeur gère souvent des données de 16 bits intermédiaires. ===Le lien avec le bus mémoire=== Le terme "mot" vous a sans doute rappelé les chapitres sur les mémoires RAM/ROM, quand nous avons parlé des mots mémoire. Pour rappel, une mémoire RAM/ROM est découpée en '''mots mémoire''', des groupes de bits qui sont transmis en une seule fois sur le bus mémoire. Par exemple, les mémoires modernes permettent de transmettre 64 bits en une seule fois sur le bus mémoire. La mémoire RAM est donc composée de groupes de 64 bits, chacun ayant une adresse mémoire, chaque groupe étant appelé une case mémoire ou encore un mot mémoire. Pour le dire autrement, il n'y a pas un octet par adresse, mais un mot mémoire dont la taille est de un ou plusieurs octets. En théorie, les notions de ''mot'' et de ''mot mémoire'' sont distinctes. La notion de mot mémoire est une définition basée sur les transferts entre processeur et mémoire RAM/ROM, alors que la notion de mot processeur est liée au jeu d'instruction et aux registres. La distinction est importante car il est possible qu'un processeur utilise des mots plus grands que le bus mémoire. par exemple, un processeur 16 bits peut utiliser un bus mémoire 8 bits, bien que cela se fasse au détriment des performances (il faut charger les opérandes en deux fois, un octet après l'autre). Il faut donc distinguer le '''mot processeur''', décrit dans la section précédente et contrasté au ''byte'', du ''mot mémoire''. Il y a alors deux possibilités principales : le bus mémoire a la même taille qu'un ''byte'', le bus a la même taille qu'un mot. En pratique, c'est la seconde solution qui est la plus utilisée, à savoir que le bus mémoire a la même taille qu'un mot processeur. Par exemple, les processeurs 16 bits gèrent des entiers codés sur 16 bits, ont des registres de 16 bits pour les entiers et adresses, et sont reliés à un bus mémoire de 16 bits. Le fait que le bus mémoire a la même taille qu'un mot a de nombreuses conséquences qu'on étudiera plus bas. : Il a existé des systèmes où le bus mémoire avait une taille égale à la moitié d'un mot processeur, mais passons. Sur les systèmes modernes, la présence de mémoires caches fait que le bus mémoire n'a pas de rapport direct avec la taille d'un mot. Le bus mémoire sert pour les transferts entre le cache et la RAM, pqui n'impliquent pas le processeur et les registres. Par contre, le taille du mot est importante pour les transferts entre cache et registres. Le bus qui relie le cache au processeur a typiquement une largeur égale à un mot mémoire. ==L'adressage par mot et par ''byte''== Pour résumer, un mot correspond à une donnée de même taille qu'un registre, ou du moins qu'un opérande/résultat entier. Un ''byte'' est une subdivision d'un mot, censé stocker un caractère, un mot contenant plusieurs ''bytes''. La différence entre mot et ''byte'' est assez peu intuitive, mais elle va devenir claire avec ce qui suit. Nous allons aborder l'adressage par mot et l'adressage par ''byte''. Le premier était utilisé sur d'anciens ordinateurs, dans les années 50 et 60, mais il permet d'introduire l'adressage par ''byte'' utilisé sur les ordinateurs modernes. ===Les architectures à adressage par mot pures=== Au tout début de l'informatique, avant les années 80, les ordinateurs ne géraient que des nombres entiers ou flottants, qui faisaient tous la même taille. La taille d'un mot, d'un nombre entier, était de 3, 4, 5, 6 7, 13, 17, 23, 36 ou 48 bits. La taille la plus courante était 36 bits, suivie par 48 bits, le reste étant assez anecdotique. Pour donner quelques exemples, l'ordinateur ERA 1103 utilisait des opérandes/résultats de 36-bits, idem sur le PDP-10. Les processeurs avaient des registres de la taille d'un mot, à savoir 36/48 bits, et le bus mémoire faisait la même taille. Un point très important est que les instructions de lecture/écriture lisaient/écrivaient des mots entiers, elles ne géraient pas de tailles autres. Par exemple, sur les processeurs 36 bits, les instructions mémoire lisaient ou écrivaient 36 bits, ni plus, ni moins. Pas de lecture/écriture sur 18 ou 12 bits, ni sur 8 ou 16 bits, par exemple. Le PDP-10, qui était un processeur 36 bits, ne gérait pas d'autre taille pour les données : c'était 36 bits pour tout le monde. De telles architectures n'utilisaient pas encore des octets, ni même de ''bytes'', qui se sont démocratisés après. La raison à cela est qu'elles étaient conçues pour faire des calculs entiers/flottants, mais pas pour gérer du texte ou l'encodage BCD. N'utiliser que des mots suffisait, car un mot correspondait à un nombre entier, seuls opérandes acceptées par le processeur. De plus, les ordinateurs de l'époque faisaient en sorte que la mémoire ait des cases mémoire de la même taille qu'un mot. Non seulement le bus mémoire avait la même taille, mais en plus, les adresses mémoire adressaient des mots entiers, pas des octets ni des ''bytes'', ni quoique ce soit d'autre. Par exemple, le PDP-10 gérait des mots de 36 bits, ce qui fait que ses registres faisaient 36 bits, mais aussi qu'une adresse adressait un bloc de 36 bits. En clair, une adresse mémoire adressait un mot complet de 36 bits, pas un octet. De tels ordinateurs étaient appelés des '''architectures à adressage par mot'''. La mémoire était donc découpée en mots, chacun avait sa propre adresse, et le processeur échangeait des mots entre registres et mémoire RAM/ROM. Il n'y avait qu'une seule unité d'adressage, ce qui fait que les bytes n'existaient pas encore. La distinction entre byte et mot est apparue après, sur des ordinateurs/processeurs différents. Par la suite, des processeurs ont permis d'adresser des données plus petites qu'un mot. L'intérêt était de faciliter la gestion du texte, qui est devenue importante quand les ordinateurs ont commencés à être utilisés en entreprise, puis dans les foyers. Les premiers ''mainframes'' étaient spécialisés dans le calcul scientifique ou dans les applications purement calculatoires, ils n'avaient pas, à gérer de texte, ce qui fait que l'adressage par mot était simple : il y avait une adresse par nombre entier, une case mémoire faisait la même taille qu'un registre, etc. Mais pour gérer du texte, c'est autre chose, car les caractères sont souvent encodés sur 6, 7, 8 bits. L'idée est alors de découper les mots en ''bytes'' de 6/7/8/9/10 bits, chacun correspondant à un caractère. ===Les architectures à adressage par mot hybrides=== Pour gérer des ''bytes'', les architectures à adressage par mot se sont adaptées. L'idée est que le processeur ajoutait des instructions pour sélectionner un byte dans le mot. Une instruction d'accès mémoire devait alors préciser deux choses : l'adresse du mot à lire/écrire, et la position du ''byte'' dans le mot adressé. Par exemple, on pouvait demander à lire le mot à l'adresse 0x5F, et de récupérer uniquement le ''byte'' numéro 6. Il s'agit d'architectures adressables par mot car une adresse identifie un mot, pas un ''byte''. La sélection des ''bytes'' se faisait avec des instructions spécialisées : le processeur lisait des mots entiers, avant que le hardware du processeur sélectionne automatiquement le byte voulu. Un exemple est le PDP-6 et le PDP-10, qui avaient des instructions de lecture/écriture de ce type. Elles prenaient trois informations : l'adresse d'un mot, la position du byte dans le mot, et enfin la taille d'un byte ! L'adressage était donc très flexible, car on pouvait configurer la taille du byte. Outre l'instruction de lecture LDB et celle d'écriture DPB, d'autres instructions permettaient de manipuler des bytes. L'instruction IBP incrémentait le numéro du pseudo-byte, par exemple. Mais de telles instructions avaient un problème : il fallait calculer l'adresse du mot mémoire et la position du ''byte'' dans le mot. Rien de compliqué en soit, mais cela demande une division et une opération modulo, ce qui n'est pas très pratique. En général, le texte est stocké dans un tableau de caractères, qui est parcouru du premier caractère vers le dernier. Gérer du texte demandait donc de gérer adresse et position : on devait incrémenter la position pour passer au caractère suivant, puis incrémenter l'adresse tous les 2/3/4/5/6 caractères. Pour éviter cela, les processeurs ont inventé l'adressage par byte. ===Les architectures à adressage par ''byte''=== L'idée derrière l''''adressage par ''byte''''' est très simple : pour le processeur, chaque ''byte'' a sa propre adresse. La mémoire est vue par le processeur comme un regroupement de ''bytes'', chacun numérotés avec une adresse. Concrètement, sur les processeurs modernes, chaque octet de la mémoire a sa propre adresse, peu importe la taille du mot processeur. Pour résumer, la distinction entre ''byte'' et mot est la suivante : le ''byte'' est utilisé pour adresser des caractères ou des octets, le mot pour adresser des entiers/flottants. Souvent, le byte est la plus petite donnée adressable et le mot est la plus grande, mais ce n'est pas systématique, quelques architectures ne respectent pas cette règle. Les processeurs à adressage par byte ont souvent plusieurs instructions de lecture/écriture, chacune pour une taille précise. Il y a au minimum une instruction de lecture qui lit un byte en mémoire, une autre qui lit un mot complet, éventuellement des instructions pour les tailles intermédiaires. Par exemple, un processeur 64 bit a des instructions pour lire 8 bits, une autre pour lire 16 bits, une autre pour en lire 32, et enfin une pour lire 64 bits. Idem pour les instructions d'écriture, et les autres instructions d'accès mémoire. Dans ce cas, le byte vaut 8 bits, le mot en fait 64, les tailles intermédiaires n'ont pas de nom. Une autre possibilité est celle d'une instruction de lecture unique, qu'on peut configurer pour lire/écrire un octet, deux, quatre, huit. Reprenons l'exemple de l'instruction LOAD qui lit une donnée en mémoire. Pour lire un ''byte'' isolé, il suffit de préciser l'adresse du ''byte'', et qu'il faut lire un seul ''byte''. Pour lire 16 ou 32 bits, l'instruction LOAD prend l'adresse du premier ''byte'' des 16/32 bits, et lit les 16/32 bits à partir de ce ''byte''. En clair, seuls les ''bytes'' ont une adresse du point de vue du processeur. Je dis du point de vue du processeur, car nous verrons que c'est plus compliqué pour ce qui est de la mémoire RAM/ROM. Ainsi, le processeur peut adresser un caractère directement, sans devoir calculer une adresse et une position : l'adresse du caractère suffit. L'avantage de l'adressage par byte est que l'on peut plus facilement modifier des données de petite taille. Par exemple, imaginons qu'un programmeur manipule du texte, avec des caractères codés sur un octet. S'il veut remplacer les lettres majuscules par des minuscules, il doit changer chaque lettre l'une après l'autre. Avec un adressage par mot, il doit lire un mot entier, modifier chaque octet en utilisant des opérations de masquage, puis écrire le mot final. Avec un adressage par byte, il peut lire chaque byte indépendamment, le modifier sans recourir à des opérations de masquage, puis écrire le résultat. Un désavantage de l'adressage par ''byte'' est que l'on adresse moins de mémoire pour un nombre d'adresses égal. Si on a un processeur qui gère des adresses de 16 bits, on peut adresser 2^16 = 65 536 adresses. Avec l'adressage par ''byte'', on ne pourra adresser que 65 536 bytes/octets, peu importe la taille du mot mémoire. Avec l'adressage par mot, on pourra adresser 65 536 mots de plusieurs octets chacun. Par exemple, avec un mot mémoire de 4 octets, on pourra adresser 65 536 × 4 octets. L'adressage par mot permet donc d'adresser plus de mémoire avec les mêmes adresses. C'est d'ailleurs pour cette raison que certains processeurs 16 bits spécialisés dans le traitement de signal (des DSP 16 bits) utilisent l'adressage par mot : pour adresser plus de mémoire avec des adresses codées sur 16 bits. [[File:Byte and word addressing.png|centre|vignette|upright=2|Adressage par mot et par Byte.]] Les architectures à adressage par byte ont d'autres défauts. Le fait qu'un mot contienne plusieurs octets/bytes a de nombreuses conséquences, desquelles naissent les contraintes d'alignement, de boutisme et autres. Dans ce qui suit, nous allons étudier les défauts des architectures adressables par byte, et allons laisser de côté les architectures adressables par mot. La raison est que toutes les architectures modernes sont adressables par byte, les seules architectures adressables par mot étant de très vieux ordinateurs aujourd'hui disparus. ===Les architectures à mot de taille variable=== D'anciens ordinateurs codaient leurs nombres sur un nombre variable de bytes, ce qui leur a valu le nom d''''architectures à mots de taille variable'''. La grande majorité étaient des architectures décimales, à savoir des ordinateurs qui utilisaient des nombres encodés en BCD ou dans un encodage similaire. De tels processeurs encodaient des nombres sous la forme d'une suite de chiffres décimaux codés en BCD sur 4 bits, voire de 5/6 bits pour les ordinateurs qui ajoutaient un bit de parité/ECC par chiffre décimal. Les calculs se faisaient chiffre par chiffre, au rythme d'un chiffre utilisé comme opérande par cycle d'horloge. Le processeur passait automatiquement d'un chiffre au suivant pour chaque opérande. Suivant l'ordinateur, la suite de chiffres était sois de même taille pour tous les nombres, soit avait une taille variable avec une taille maximale, soit n'avait tout simplement pas de taille maximale. Ce sont ces deux derniers cas qui intéressent ici. Le premières architectures mot variable encodaient un entier BCD avec : une suite de chiffres BCD, précédée par un ou deux ''bytes'' pour encoder la longueur de la suite de chiffres. Les opérandes avaient ainsi une taille maximale, qui dépendait du codage de la longueur, du nombre de bits utilisés l'encoder. Par exemple, si on utilisait 8 bits pour encoder la longueur, les opérandes allaient de 0 à 255 chiffres BCD. Mais la plupart des ordinateurs ne gérait pas des tailles aussi importantes. Par exemple, de nombreux processeurs IBM géraient des opérandes allant jusqu'à 10 chiffres BCD maximum, pas plus. D'autres architectures codaient les nombres par des chaines de caractères terminées par un byte de terminaison. La chaine de caractère contenait uniquement des chiffres, codés en BCD. Les bytes stockaient chacun un caractère, qui était utilisé pour encoder soit un chiffre décimal, soit un byte de terminaison. La taille d'un caractère était généralement de 5/6 bits, vu qu'il fallait au minimum coder les chiffres BCD et des symboles supplémentaires. Un exemple est celui des IBM 1400 series, qui utilisaient des chaines de caractères séparées par deux bytes : un byte de ''wordmark'' au début, et un byte de ''record mark'' à la fin. Les caractères étaient codés sur 6 bits. Chaque caractère/chiffre avait sa propre adresse, ce qui fait que l'architecture est techniquement adressable par byte, alors que les mots correspondaient aux nombres de taille variable. ===L'adressage à la granularité d'un bit=== Nous venons de voir que l'adressage par byte est utile pour modifier des caractères sans avoir à modifier un mot complet. Sans adressage par ''byte'', modifier un caractère demande de lire un mot entier, de modifier le caractère, puis d'enregistrer le mot final. Maintenant, rappelons-nous les chapitres sur les opérations logiques. Nous avions vu qu'il est fréquent de faire la même chose, mais avec des bits : modifier certains bits d'un nombre, sans modifier les autres. Il arrive occasionnellement que les programmeurs utilisent des ''bitfields'', à savoir qu'ils mémorisent plusieurs informations dans un seul entier. Un exemple classique est le stockage des dates : on stocke le jour, le mois et l'année dans un seul entier, dont quelques bits encodent la journée, d'autres le mois, d'autres l'année. De telle situations demandent de modifier certains bits d'un ''bitfield'', mais pas les autres. Un autre exemple est celui de la configuration des périphériques. Nous en avions déjà parlé rapidement dans le chapitre "l'architecture de base", mais configurer des périphériques demande de modifier la valeur de registres de configuration, qui contiennent justement des ''bitfields''. Par exemple, pour configurer une carte graphique, il y a un registre pour configurer la résolution et la fréquence d'affichage, ces trois nombres étant concaténés et stockés dans un registre. De plus, la plupart des registres de configuration disposent de bits qui permettent d'activer ou de désactiver des fonctionnalités. Activer ou désactiver une fonctionnalité demande alors de modifier un seul bit dans le registre de configuration adéquat. Les registres de configuration des périphérique sont adressés avec une adresse mémoire. Et cela peu importe qu'ils soit mappés en mémoire RAM, ou dans un espace d'adressage séparés : il y a toujours une adresse mémoire bien précise qui permet d'adresser un registre. Vous l'avez sans doute vu venir, mais modifier un seul bit demande de charger un mot/''byte'' complet, de modifier le bit dans les registres, puis d'enregistrer le ''byte''/mot final. Encore une fois, on doit effectuer un '''accès en lecture-modification-écriture''' (''Read-Modify-Write''). Et cela pose de nombreux problèmes techniques dont on ne pas vraiment parler pour le moment, notamment des problèmes d'atomicité sur les architectures multicœurs. Et au-delà de ces problèmes techniques, ce n'est pas pratique pour les programmeurs ou les compilateurs. Heureusement, à problème identique, solution identique. Une première solution a été d'ajouter des instructions processeurs capables d'effectuer l'accès en lecture-modification-écriture automatiquement. Les instructions en question sont des instructions ''Clear Bit'', ''Set Bit'', ''Invert Bit'', et quelques autres. Elles ne se contentent pas de faire une opération logique, mais font aussi la lecture et l'écriture du résultat en mémoire RAM. L'instruction prend comme opérandes deux informations : l'adresse de l'octet qui contient le bit à modifier, la position de ce bit dans l'octet. Il faut évidemment les calculer, rien de compliqué, dans le programmeur doit faire cela à la main. Il s'agit d'une solution similaire à celle des architecture à adressage par mot hybrides, avec des instructions pour gérer des ''bytes''. Et enfin, une autre solution utilise l''''adressage à la granularité d'un bit''', que nous raccourcirons en '''adressage par bit'''. Ce terme compliqué cache un concept tout simple : il n'y a pas une adresse par ''byte'', ni une adresse par mot processeur/mémoire, mais une adresse pour chaque bit de la mémoire ! S'il a existé des processeurs et des mémoires bit-adressables, il faut avouer qu'ils sont très rares. De tels processeurs gardent la capacité d'adresser un ''byte'' ou un mot. L'adresse d'un ''byte''/mot étant l'adresse de son premier bit, de la même manière que l'adresse d'un mot est l'adresse de son premier ''byte'' avec l'adressage par ''byte''. Sur ces architectures, le ''byte'' n'est pas la plus petite unité mémoire adressable. Il est possible d'avoir un adressage par bit au niveau du processeur, alors que les registres de configurations ne permettent pas d'adresser un bit distinct. Dans ce cas, c'est soit le périphérique, soit le processeur, qui se chargent d'effectuer un accès en lecture-modification-écriture en sous-main, de manière automatique, sans que le programmeur ait quoique ce soit à faire. Une des rares utilisation actuelle de l'adressage par bit est la technique dite de '''''bit-banding'''''. Elle est présente sur certains processeurs ARM, potentiellement d'autres. L'idée est que l'on peut manipuler les registres de configuration de deux manières : soit avec l'adressage par byte/mot, soit avec l'adressage par bit. Un registre de configuration a alors plusieurs adresses : une adresse globale pour le modifier entièrement en écrivant un ''byte''/mot dedans, une adresse pour chacun de ses bit. Les deux adresses peuvent en théorie être dans deux espaces d'adressage séparés, mais ce n'est pas la solution retenue sur les processeurs ARM, qui mettent ces adresses dans un seul espace d'adressage. Elle ne concerne qu'une petite partie de la mémoire RAM de l'ordinateur, à savoir deux blocs de 1 mébi-octet sur l'ARM Cortex M3. Le premier bloc est mappé dans l'intervalle allant des adresses 0x20000000h à 0x20100000h, ses bits sont adressés dans l'intervalle allant des adresses 0x22000000h à 0x23FFFFFFh. Le seconde intervalle est plus grand, car le premier utiliser une adresse par mot, le second a une adresse par bit. ==L'implémentation matérielle de l'adressage par byte== Implémenter l'adressage par ''byte'' demande de faire quelques modifications au niveau du processeur, mais aussi parfois du bus mémoire et de la mémoire RAM. Avant de poursuivre, rappelons que la notion de byte est avant tout liée au jeu d'instruction, mais qu'elle ne dit rien du bus mémoire ! Il est parfaitement possible d'utiliser un bus mémoire d'une taille différente de celle du byte ou du mot. La largeur du bus mémoire, la taille d'un mot, et la taille d'un byte, ne sont pas forcément corrélées. Néanmoins, deux implémentations principales sont possibles. La première utilise une mémoire dont chaque case mémoire est un ''byte''. Le bus mémoire a la même taille qu'un ''byte'', ce qui fait que les lectures/écritures se font ''byte'' par ''byte'', ce qui est très lent. Une autre solution utilise un bus mémoire de la même taille qu'un mot. Dans ce cas, le processeur lit/écrit des mots mémoire entiers, mais il peut sélectionner les ''bytes'' qui l'intéressent si besoin. Il peut donc lire/écrire un mot entier en une seule fois s'il manipule des entiers, mais ne garder que les ''bytes'' adéquats quand il travaille sur du texte. ===Les architectures avec une mémoire adressable par byte=== La première implémentation a un bus mémoire de la taille d'un ''byte'', qui transmet un ''byte'' à la fois. En clair, la largeur du bus mémoire est celle du byte. Le moindre accès mémoire se fait byte par byte, donc en plusieurs cycles d'horloge. Par exemple, sur un processeur 64 bits, la lecture d'un mot complet se fera octet par octet, ce qui demandera 8 cycles d'horloge, cycles d'horloge mémoire qui plus est. Par contre, lire ou écrire un ''byte'' se fera en un seul cycle d'horloge mémoire, ce qui n'est pas le cas avec l'autre implémentation. Cette implémentation a donc un désavantage en termes de performance quand on manipule des mots, mais un avantage quand on manipule des ''bytes''. La performance dépend de plus de la taille des données lue/écrites. On prend moins de temps à lire une donnée courte qu'une donnée longue. Un autre avantage est qu'on peut lire ou écrire un mot, peu importe son adresse. Pour donner un exemple, je peux parfaitement lire une donnée de 16 bits à l'adresse 4, puis lire une autre donnée de 16 bits à l'adresse 5 sans aucun problème. En conséquence, il n'y a pas de contraintes d'alignements et les problèmes que nous allons aborder dans la suite n'existent pas. [[File:Chargement d'une donnée sur un processeur sans contraitnes d'alignement.jpg|centre|vignette|upright=2|Chargement d'une donnée sur un processeur sans contraintes d'alignement.]] ===Les architectures avec une mémoire adressable par mot=== Avec la seconde implémentation, le bus mémoire a la largeur nécessaire pour lire un mot entier. Il y a alors confusion entre un mot au sens du jeu d'instruction, et un mot mémoire. Pour rappel, une donnée qui a la même taille que le bus de données est appelée un mot mémoire. Le processeur peut lire/écrire un mot mémoire entier dans ses registres, en un seul accès mémoire, en un cycle d'horloge mémoire. Il y a donc un avantage en termes de performance quand on manipule des mots. Pour la lecture/écriture de données plus petites qu'un mot, tout dépend de si on fait une lecture ou une écriture. Lire un ''byte'' prend le même temps : il suffit de lire un mot entier et de ne sélectionner que le ''byte'' voulu. Et il en est de même pour les tailles intermédiaires entre mot et ''byte'' : le processeur charge un mot complet, puis sélectionne les octets adéquats, grâce un multiplexeur. [[File:Standard Bus.JPG|centre|vignette|upright=2|Accès d'un mot et d'un octet.]] La gestion des écritures est par contre désavantagée. Le problème est que les écritures se font mot par mot, pas ''byte'' par ''byte''. Ne modifier qu'un seul ''byte'' demande de lire le mot qui contient le ''byte'' à modifier, modifier le ''byte'', et enfin écrire le résultat. Et le même problème a lieu pour les tailles intermédiaires. Par exemple, pour un processeur 32 bits, écrire 16 bits demande de lire un mot de 32 bits, ne modifier que les 126 bits adéquats dedans, et écrire le résultat dans le mot mémoire final. Le tout demande des circuits de masquage et de décalage assez complexe. [[File:Exemple du chargement d'un octet dans un registre de trois octets.jpg|centre|vignette|upright=2|Exemple du chargement d'un octet dans un registre de trois octets.]] Un problème avec cette implémentation est que l'adressage de la mémoire et du CPU ne sont pas compatibles : le processeur utilise une adresse par byte, la mémoire une adresse par mot mémoire ! Il faut donc distinguer les adresses gérées par la mémoire et celles gérées par le processeur. Le processeur génère des '''adresses d'octet''', qui permettent de sélectionner un octet bien précis (remplacez octet par ''byte'' pour plus de généralité). L'octet en question est dans une case mémoire bien précise, qui a elle-même une '''adresse mémoire'''. Lors d'un accès mémoire, l'adresse d'octet est convertie en une adresse mémoire, la case mémoire entière est lue, puis le processeur ne récupère que les données adéquates. Pour cela, des circuits d'alignement mémoire se chargent de faire la conversion entre adresses du processeur et adresse mémoire, nous allons détailler comment ils fonctionnent dans ce qui suit. [[File:Chargement d'une donnée sur un processeur avec contraintes d'alignement.jpg|centre|vignette|upright=2|Chargement d'une donnée sur un processeur avec contraintes d'alignement.]] Par convention, l'adresse d'un mot est l'adresse de son octet de poids faible. Les autres octets du mot ne sont pas adressables par la mémoire. Par exemple, si on prend un mot de 8 octets, on est certain qu'une adresse sur 8 disparaîtra. L'adresse du mot est utilisée pour communiquer avec la mémoire, mais cela ne signifie pas que l'adresse des octets est inutile au-delà du calcul de l'adresse du mot. En effet, l'accès à un octet précis demande de déterminer la position de l'octet dans le mot à partir de l'adresse de l’octet. Prenons un processeur ayant des mots de 4 octets et répertorions les adresses utilisables. Le premier mot contient les octets d'adresse 0, 1, 2 et 3. L'adresse zéro est l'adresse de l'octet de poids faible et sert donc d'adresse au premier mot, les autres sont inutilisables sur le bus mémoire. Le second mot contient les adresses 4, 5, 6 et 7, l'adresse 4 est l'adresse du mot, les autres sont inutilisables. Et ainsi de suite. Si on fait une liste exhaustive des adresses valides et invalides, on remarque que seules les adresses multiples de 4 sont utilisables. Et ceux qui sont encore plus observateurs remarqueront que 4 est la taille d'un mot. Dans l'exemple précédent, les adresses utilisables sont multiples de la taille d'un mot. Sachez que cela fonctionne quelle que soit la taille du mot. Si N est la taille d'un mot, alors seules les adresses multiples de N seront utilisables. Avec ce résultat, on peut trouver une procédure qui nous donne l'adresse d'un mot à partir de l'adresse d'un octet. Si un mot contient N bytes, alors l'adresse du mot se calcule en divisant l'adresse du byte par N. La position du byte dans le mot est quant à elle le reste de cette division. Un reste de 0 nous dit que l'octet est le premier du mot, un reste de 1 nous dit qu'il est le second, etc. Et c'est cette position qui est utilisée pour configurer les multiplexeurs, quand on accède à un byte isolé. [[File:Adresse d'un mot avec alignement mémoire strict.png|centre|vignette|upright=2|Adresse d'un mot avec alignement mémoire strict.]] Le processeur peut donc adresser la mémoire RAM en traduisant les adresses des octets en adresses de mot. Il lui suffit de faire une division pour cela. Il conserve aussi le reste de la division dans un registre pour sélectionner l'octet une fois la lecture terminée. Un accès mémoire se fait donc comme suit : il reçoit l'adresse à lire, il calcule l'adresse du mot, effectue la lecture, reçoit le mot à lire, et utilise le reste pour sélectionner l'octet final si besoin. La dernière étape est facultative et n'est présente que si on lit une donnée plus petite qu'un mot. La division est une opération assez complexe, mais il y a moyen de ruser. L'idée est de faire en sorte que N soit une puissance de deux. La division se traduit alors par un vulgaire décalage vers la droite, le calcul du reste par une simple opération de masquage. C'est la raison pour laquelle les processeurs actuels utilisent des mots de 1, 2, 4, 8 octets. Sans cela, les accès mémoire seraient bien plus lents. De plus, cela permet d'économiser des fils sur le bus d'adresse. Si la taille d'un mot est égale à <math>2^{n}</math>, seules les adresses multiples de <math>2^{n}</math> seront utilisables. Or, ces adresses se reconnaissent facilement : leurs n bits de poids faibles valent zéro. On n'a donc pas besoin de câbler les fils correspondant à ces bits de poids faible. ==L'alignement mémoire== Dans la section précédente, nous avons évoqué le cas où un processeur à adressage par byte est couplé à une mémoire adressable par mot. Sur de telles architectures, des problèmes surviennent quand les lectures/écritures se font par mots entiers. Le processeur fournit l'adresse d'un byte, mais lit un mot entier à partir de ce byte. Par exemple, prenons une lecture d'un mot complet : celle-ci précise l'adresse d'un byte. Sur un CPU 64 bits, le processeur lit alors 64 bits d'un coup à partir de l'adresse du byte. Et cela peut poser quelques problèmes, dont la résolution demande de respecter des restrictions sur la place de chaque mot en mémoire, restrictions résumées sous le nom d''''alignement mémoire'''. ===L'exemple sur les processeurs 16 bits=== Pour faire comprendre ce qu'est l'alignement mémoire, nous allons prendre l'exemple d'un processeur 16 bits connecté à une mémoire de 16 bits, via un bus mémoire de 16 bits. Le processeur utilisant l'adressage par byte, chaque octet de la mémoire a sa propre adresse. Par contre, le processeur lit et écrit des paquets de 16 bits, soit deux octets. : Pour rappel, un groupe de deux octets est appelé un '''doublet'''. Pour la mémoire, les octets sont regroupés en groupes de deux, en '''doublets mémoire'''. Pour la mémoire, un doublet mémoire a une adresse unique. Et cela ne colle pas avec l'adressage par byte utilisé par le processeur, il y a une différence entre les adresses du processeur et celles de la mémoire. Il y a une adresse par doublet pour la mémoire, une adresse par octet pour le processeur. L'adresse mémoire a donc un bit de moins que l'adresse processeur. Pour faire la distinction, nous utiliserons les termes : ''adresse mémoire'' et ''adresse processeur''. Lorsque le processeur lit un doublet, il lit le premier octet à une adresse processeur et l'octet suivant dans l'adresse processeur suivante. Les adresses processeur sont donc regroupées par groupes de deux : l'adresse 0 et 1 adressent toutes deux le premier doublet, l'adresse 2 et 3 adressent le second doublet, etc. Un doublet est donc identifié par deux adresses processeur : une '''adresse paire''' et une '''adresse impaire'''. Nous allons partir du principe que l'octet de poids faible est dans l'adresse paire, l'octet de poids fort dans l'adresse impaire. Les règles de boutisme autorisent de faire l'inverse, mais ce n'est pas le choix le plus intuitif. L'alignement mémoire dit quoi faire lorsque le processeur veut lire/écrire 16 bits à une adresse impaire. S'il veut lire 16 bits à une adresse impaire, les deux octets seront dans des doublets mémoire différents. Le premier sera l'octet de poids fort d'un doublet, l'autre sera l'octet de poids faible du doublet suivant. Le doublet que le processeur veut lire/écrire est à cheval sur deux doublets mémoire. Et le processeur ne gère pas cette situation naturellement. [[File:Alignement mémoire sur 16 bits.png|centre|vignette|upright=2|Alignement mémoire sur 16 bits]] Pour résoudre ce problème, il y a deux solutions : imposer l'alignement mémoire, supporter les accès non-alignés. L'alignement mémoire strict n'autorise que les accès mémoire à des adresses mémoires paires. Tout accès à une adresse mémoire impaire lève une exception matérielle, signe que c'est une erreur matérielle. Par contre, les adresses paires sont autorisées car une adresse paire identifie un doublet mémoire, que le processeur peut lire/écrire en une seule fois, à travers le bus mémoire. Le processeur gère donc uniquement des lectures/écritures de doublet, au sens de doublet mémoire. Sans alignement mémoire, on peut lire/écrire 16 bits à partir d'une adresse paire comme impaire, les deux sont autorisées. Pour cela, le processeur doit gérer les lectures/écritures de 16 bits à des adresses impaires. Vu que les 16 bits demandés sont à cheval sur deux doublets mémoire, le processeur doit lire les deux doublets mémoire, sélectionner les octets adéquats, et les concaténer pour obtenir les 16 bits finaux. Pour ce qui est de lire/écrire des octets, l'alignement mémoire ne pose aucune contrainte, vu qu'un octet n'est jamais à cheval sur un doublet, quadruplet ou autre. Le processeur peut demander à lire un seul octet, mais la mémoire fournira deux octets et le processeur devra n'en conserver qu'un. Donc, il faut prévoir un système pour sélectionner l'octet demandé dans un doublet. Les écritures sont plus complexes, car elles demandent de lire un doublet, de modifier l'octet adéquat, et de réécrire le doublet final en RAM. Voyons ce qu'il en est pour les lectures d'octets. Pour la lecture d'un octet à une adresse paire, le processeur lit un doublet et masque l'octet de poids fort, chargé en trop. Pour une lecture d'un octet à une adresse impaire, le processeur lit un doublet, déplace l'octet de poids fort dans l'octet de poids faible, et masque l'octet inutile. Dans tous les cas, le processeur ne lit/écrit qu'un doublet à la fois et sélectionne les octets demandés. Le tout est réalisé par un circuit intégré au processeur, qui est directement relié au bus de données. Il prend en entrée deux bits : le premier indique s'il faut faire une lecture sur 8 ou 16 bits, le second qui indique s'il faut lire l'octet de poids faible ou fort. Le circuit est alors composé d'un circuit de masquage et d'un multiplexeur. Le multiplexeur est utilisé pour choisir l'octet de poids faible ou fort. Le circuit de masquage met à zéro l'octet de poids fort en cas d'accès sur 8 bits. [[File:Circuit d'accès 16-8 bits.png|centre|vignette|upright=2|Circuit d'accès 16-8 bits]] Une autre possibilité est celle utilisée sur les anciens processeurs Intel 16 bits, qui utilisaient deux bits. Le premier est le bit de poids fort de l'adresse processeur, qui indiquait si l'adresse est paire ou non. Le second bit, nommé BHE, indique s'il fallait masquer l'octet de poids fort ou non. {|class="wikitable" |- ! ! Adresse paire ! Adresse impaire |- ! BHE = 0 | Lecture 16 bits | Lecture octet de poids fort |- ! BHE = 1 | Lecture octet de poids faible | Pas de lecture |} ===L'alignement mémoire des données=== L'exemple précédent nous a montré ce qu'il en était sur les processeurs 16 bits. Mais pour généraliser le concept, nous allons voir le cas des processeurs 32 bits et plus. Nous venons de voir que l'alignement mémoire sur 16 bits impose des contraintes quant à l'adressage de la mémoire, en empêchant de lire des données qui sont à cheval sur deux doublets. Sur 32 bits, c'est la même chose, mais avec des quadruplets (des groupes de 4 octets, soit 32 bits) : impossible de lire des données si elles sont à cheval sur deux quadruplets mémoire. Sur 64 bits, c'est la même chose, mais avec des octuplets de 8 octets/ 64 bits : impossible de lire des données si elles sont à cheval sur deux octuplets mémoire. La différence, c'est que la situation peut se présenter même si on ne lit pas 32/64 bits. Imaginons le cas particulier suivant : je dispose d'un processeur utilisant des mots de 4 octets. Je dispose aussi d'un programme qui doit manipuler un caractère stocké sur 1 octet, un entier de 4 octets et une donnée de deux octets. Mais un problème se pose : le programme qui manipule ces données a été programmé par quelqu'un qui n'était pas au courant de ces histoire d'alignement, et il a répartit mes données un peu n'importe comment. Supposons que cet entier soit stocké à une adresse non-multiple de 4. Par exemple : {|class=wikitable |- !Adresse !Octet 4 !Octet 3 !Octet 2 !Octet 1 |- |0x 0000 0000 |Caractère |Entier |Entier |Entier |- |0x 0000 0004 |Entier |Donnée |Donnée |- |0x 0000 0008 | | | | |} La lecture ou écriture du caractère ne pose pas de problème, vu qu'il ne fait qu'un seul byte. Pour la donnée de 2 octets, c'est la même chose, car elle tient toute entière dans un mot mémoire. La lire demande de lire le mot et de masquer les octets inutiles. Mais pour l'entier, ça ne marche pas car il est à cheval sur deux mots ! On dit que l'entier n'est pas aligné en mémoire. En conséquence, impossible de le charger en une seule fois Pour résumer, avec un bus mémoire de 32 bits, le problème peut survenir si le processeur veut lire 32 bits, mais aussi 16 bits. Si on veut lire 16 bits, mais que le premier octet est dans un quadruplet, et l'autre octet dans un autre quadruplet, l'alignement mémoire intervient. Idem si on veut lire 4 octets, mais que les 3 premiers sont dans un quadruplet, pas le dernier. Tout cela est plus facile à comprendre avec un exemple. La situation est gérée différemment suivant le processeur. Sur certains processeurs, la donnée est chargée en deux fois : c'est légèrement plus lent que la charger en une seule fois, mais ça passe. On dit que le processeur gère des accès mémoire non-alignés. D'autres processeurs ne gérent pas ce genre d'accès mémoire et les traitent comme une erreur, similaire à une division par zéro, et lève une exception matérielle. Si on est chanceux, la routine d'exception charge la donnée en deux fois. Mais sur d'autres processeurs, le programme responsable de cet accès mémoire en dehors des clous se fait sauvagement planter. Par exemple, essayez de manipuler une donnée qui n'est pas "alignée" dans un mot de 16 octets avec une instruction SSE, vous aurez droit à un joli petit crash ! Pour éviter ce genre de choses, les compilateurs utilisés pour des langages de haut niveau préfèrent rajouter des données inutiles (on dit aussi du bourrage) de façon à ce que chaque donnée soit bien alignée sur le bon nombre d'octets. En reprenant notre exemple du dessus, et en notant le bourrage X, on obtiendrait ceci : {|class=wikitable |- !Adresse !Octet 4 !Octet 3 !Octet 2 !Octet 1 |- |0x 0000 0000 |Caractère |X |X |X |- |0x 0000 0004 |Entier |Entier |Entier |Entier |- |0x 0000 0008 |Donnée |Donnée |X |X |} Comme vous le voyez, de la mémoire est gâchée inutilement. Et quand on sait que de la mémoire cache est gâchée ainsi, ça peut jouer un peu sur les performances. Il y a cependant des situations dans lesquelles rajouter du bourrage est une bonne chose et permet des gains en performances assez abominables (une sombre histoire de cache dans les architectures multiprocesseurs ou multi-cœurs, mais je n'en dit pas plus). L'alignement mémoire se gère dans certains langages (comme le C, le C++ ou l'ADA), en gérant l'ordre de déclaration des variables. Essayez toujours de déclarer vos variables de façon à remplir un mot intégralement ou le plus possible. Renseignez-vous sur le bourrage, et essayez de savoir quelle est la taille des données en regardant la norme de vos langages. ===L'alignement des instructions en mémoire=== Le processeur peut incorporer des contraintes sur l'alignement des instructions, au même titre que les contraintes d'alignement sur les données vues précédemment. Concrètement, il vaut mieux que les instructions rentrent dans un mot mémoire. La raison est que si on veut lire une instruction en un seul cycle, il faut qu'une instruction rentre toute entière dans le bus mémoire, donc dans un mot mémoire. Les processeurs RISC se débrouillent donc pour que les instructions rentrent dans un mot mémoire, donc dans un mot processeur. Les instructions sont donc alignées sur un mot mémoire, elles doivent respecter les mêmes contraintes d'alignement que les données. Elles peuvent être plus courtes ou plus longues qu'un mot, mais elles doivent commencer à la première adresse d'un mot mémoire. La conséquence est que les instructions sont placées à des adresses précises. Par exemple, prenons un ordinateur avec des mots mémoire de 8 octets. La première instruction prend les 8 premiers octets de la mémoire, la seconde prend les 8 octets suivants, etc. En faisant cela, l'adresse d'une instruction est toujours un multiple de 8. Et on peut généraliser pour toute instruction de taille fixe : si elle fait X octets, son adresse est un multiple de X. Généralement, on prend X une puissance de deux pour simplifier beaucoup de choses. Notamment, cela permet de simplifier le ''program counter'' : quelques bits de poids faible deviennent inutiles. Par exemple, si on prend des instructions de 4 octets, les adresses des instructions sont des multiples de 4, donc les deux bits de poids faible de l'adresse sont toujours 00 et ne sont pas intégrés dans le ''program counter''. Le ''program counter'' est alors plus court de deux bits. Idem avec des instructions de 8 octets qui font économiser 3 bits, ou avec des instructions de 16 octets qui font économiser 4 bits. Les processeurs CISC, avec leurs instructions de longueur variable potentiellement lues en plusieurs fois, ne sont pas concernés. Les instructions de taille variable ne sont généralement pas alignées. Sur certains processeurs, les instructions n'ont pas de contraintes d'alignement du tout. Leur chargement est donc plus compliqué et demande des méthodes précises qui seront vues dans le chapitre sur l'unité de chargement du processeur. Évidemment, le chargement d'instructions non-alignées est donc plus lent. En conséquence, même si le processeur supporte des instructions non-alignées, les compilateurs ont tendance à aligner les instructions comme les données, sur la taille d'un mot mémoire, afin de gagner en performance. D'autres architectures CISC ont des contraintes d'alignement bizarres, pour des raisons historiques. Par exemple, les premiers processeurs x86 16 bits imposaient des instructions alignées sur 16 bits et cette contrainte est restée sur les processeurs 32 bits. Que ce soit pour des instructions de taille fixe ou variables, les circuits de chargement des instructions et les circuits d'accès mémoire ne sont pas les mêmes, ce qui fait que leurs contraintes d'alignement peuvent être différentes. On peut avoir quatre possibilités : des instructions non-alignées et des données alignées, l'inverse, les deux qui sont alignées, les deux qui ne sont pas alignées. Par exemple, il se peut qu'un processeur accepte des données non-alignées, mais ne gère pas des instructions non-alignées ! Le cas le plus simple, fréquent sur les architectures RISC, est d'avoir des instructions et données alignées de la même manière. De plus, sur les processeurs où les deux sont alignés, on peut avoir un alignement différent pour les données et les instructions. Par exemple, pour un processeur qui utilise des instructions de 8 octets, mais des données de 4 octets. Les différences d'alignements posent une contrainte sur l'économie des bits sur le bus d'adresse. Il faut alors regarder ce qui se passe sur l'alignement des données. Par exemple, pour un processeur qui utilise des instructions de 8 octets, mais des données de 4 octets, on ne pourra économiser que deux bits, pour respecter l'alignement des données. Ou encore, sur un processeur avec des instructions alignées sur 8 octets, mais des données non-alignées, on ne pourra rien économiser. Les architectures CISC utilisent souvent des contraintes d'alignement, avec généralement des instructions de taille variables non-alignées, mais des données alignées. Les deux dernières possibilités ne sont presque jamais utilisées. Un cas particulier est celui de l'Intel iAPX 432, dont les instructions étaient non-alignées au niveau des bits ! Leur taille variable faisait que la taille des instructions n'était pas un multiple d'octets. Il était possible d'avoir des instructions larges de 23 bits, d'autres de 41 bits, ou toute autre valeur non-divisible par 8. Un octet pouvait contenir des morceaux de deux instructions, à cheval sur l'octet. Ce comportement fort peu pratique faisait que l'implémentation de l'unité d"e chargement était complexe. ===L'alignement mémoire et le bus de données=== L'alignement mémoire parait simple à implémenter. Il suffit de connecter le processeur à la mémoire, on lit des blocs de 16/32/64 bits, et le processeur gère les accès non-alignés en interne. Mais ces questions d'alignement surviennent aussi pour l'accès aux entrées-sorties, aux périphériques. Et les périphériques n'ont pas forcément l'alignement adéquat. Par exemple, il arrive qu'un processeur 64 bits, qui gère des données alignées sur 64 bits, doive faire avec des périphériques 32 bits ou 16 bits, pour des raisons de compatibilité. Et le processeur doit gérer la situation. Avant de poursuivre, faisons quelques rappels. Il y a deux solutions pour relier les périphériques au processeur. La première utilise un bus dédié pour les périphériques, séparé du bus pour la mémoire RAM/ROM. Une autre solution utilise un bus unique connecté à la fois à la RAM et aux périphériques. Dans les deux cas, il est possible de connecter des périphériques 8 ou 16 bits à un bus plus large, par exemple à un bus de données 32 ou 64 bits. Il y a juste des bits en trop, qui ne servent pas quand on communique avec un périphérique 8 ou 16 bits. Il n'est pas nécessaire d'éliminer les bits en trop, ils sont simplement mis à zéro par le processeur. Cependant, certains processeurs ont implémenté une technique pour éviter tout problème. Un exemple typique est le processeur Intel 486, un processeur 32 bits. Pour sélectionner les octets adéquats, le 486 disposait de 4 broches de sorties, nommées BE0 - BE3 (''Byte Enable'' 0 à 3). Les bits ''Byte Enable'', associés à chaque broche, servaient à masquer certains octets lors de la transmission. Il y avait un bit ''Byte Enable'' par octet. Si le bit ''Byte Enable'' associé était à 1, l'octet était alors ignoré lors de la transmission sur le bus de données. Cela permettait de masquer des octets lors d'une transmission 32 bits, mais aussi de gérer les transactions 8 et 16 bits. Par exemple, pour une transaction 16 bits, les bits ''Byte Enable'' masquaient soit les deux octets de poids fort, soit les deux octets de poids faible. Pour les transactions d'octet, les trois octets inutiles étaient masqués. [[File:Intel486 32-bit IO Interface.png|centre|vignette|upright=2|Intel 486 Interface 32-bit.]] Plus haut, nous avons vu ce qu'il se passe quand un CPU 16 bits lit un octet individuel. Nous avions vu que le processeur lisait un doublet complet, et qu'un multiplexeur sélectionnait l'octet adéquat dans ce doublet. Il se passe la même chose avec un processeur 32 bits, sauf que la situation est plus complexe. Le CPU doit gérer à la fois des doublets et des octets. Dans une donnée de 32 bits, il doit pouvoir sélectionner soit le doublet de poids fort, soit celui de poids faible. Pour les octets, il doit pouvoir sélectionner l'un des quatre octet. Là encore, cela se fait avec des multiplexeurs, qu'il faut configurer. : Il y a le même problème en sens inverse, pour les écritures, qui se résoud lui aussi avec des multiplexeurs, séparés du précédent. [[File:Intel486 Bus Swapping 16-bit interface.png|centre|vignette|upright=2|Multiplexeur du bus de données, pour une transmission 16-bits.]] Maintenant, voyons ce qu'il en est pour l'alignement mémoire. Prenons un processeur de 64 bits, qui utilise des données alignées sur 64 bits (8 octets). Une implémentation avec alignement strict utilise des adresses processeur de 64 bits auxquelles on retire 3 bits pour gérer l'alignement. L'adresse mémoire fait donc 61 bits, et le bus d'adresse a la même taille. Une telle situation ne permet que de gérer des périphériques 64 bits, mais n'est pas compatible avec des périphériques de 32, 16 ou 8 bits. Une solution simple utilise des adresses mémoires complètes, identiques aux adresses processeur. Pour reprendre l'exemple d'un CPU 64 bit, avec un bus d'adresse de 64 bits. Il envoie une adresse de 64 bits aux entrées-sorties, celles-ci répondent en envoyant une donnée de 8-16-32 bits sur le bus de données. Cette solution marche bien quand les périphériques disposent d'un bus dédié. Le fait d'avoir un bus dédié pour les périphériques permet d'utiliser des adresses complètes facilement. Le bus dédié aux périphérique utilise des adresses complètes, alors que le bus mémoire utilise des adresses tronquées pour tenir compte de l'alignement mémoire. Mais il a existé quelques processeurs qui faisaient autrement. Prenons l'exemple du processeur 486 d'Intel, un processeur 32 bits. Il gérait que des données alignées sur 32 bits, soit 4 octets. Intuitivement, on se dit qu'il avait pour cela un bus d'adresse de 32 bits, mais ce n'était pas le cas. À la place, il avait un bus d'adresse de 30 bits, qui correspondait à une adresse de 32 bits dont les deux bits de poids faible étaient à zéro. Pour les mémoires et périphériques 32 bits, tout allait comme sur des roulettes. On leur envoyait une adresse de 30 bits, ils répondaient avec une donnée de 32 bits, rien de plus. Mais pour les périphériques 16 et 8 bits, il fallait générer les bits manquants de l'adresse. Et pour cela, il utilisait les bits ''Byte Enable'' vus plus haut. [[File:Alignement mémoire des périphériques.png|centre|vignette|upright=2|Alignement mémoire des périphériques]] En effet, certaines combinaisons des bits ''Byte Enable'' n'activaient qu'un seul octet ou qu'un seul doublet sur le bus de données. Et suivant la position de l'octet/doublet dans les 32 bits, on peut en déduire leur adresse. Par exemple : * Si seul l'octet de poids faible est activé, alors les deux bits manquants de l'adresse valent 00. * Si seul le second octet est activé, alors les deux bits manquants de l'adresse valent 01. * Si seul le troisième octet est activé, alors les deux bits manquants de l'adresse valent 10. * Si seul l'octet de poids fort est activé, alors les deux bits manquants de l'adresse valent 1. Il y a la même chose avec les doublets, sauf que le bit de poids faible de l'adresse complète vaut alors 0. On a alors deux situations : * Si seul le doublet de poids faible est activé, alors les deux bits manquants de l'adresse valent 00. * Si seul le doublet de poids fort est activé, alors les deux bits manquants de l'adresse valent 10. Un circuit de ''byte select'' générait les bits manquant de l'adresse à partir des bits ''Byte Enable''. Le circuit en question est formellement un '''circuit de décodage d'adresse''', comme on l'a vu dans les chapitre sur le bus. De plus, il générait les signaux BHE et BLE, qu'on a abordé plus haut, si la mémoire ou le périphérique les supportent. Le circuit pour cela. Jugez-en par vous-même : [[File:Intel486 Logic A1 BHE BLE for 16-bit.png|centre|vignette|upright=2|Intel486 Logic A1 BHE BLE for 16-bit]] ==Le boutisme : une spécificité de l'adressage par byte== Un autre problème lié à l'adressage par byte est lié au fait que l'on a plusieurs bytes par mot : dans quel ordre placer les bytes dans un mot ? On peut introduire le tout par une analogie avec les langues humaines : certaines s’écrivent de gauche à droite et d'autres de droite à gauche. Dans un ordinateur, c'est pareil avec les bytes/octets des mots mémoire : on peut les écrire soit de gauche à droite, soit de droite à gauche. Quand on veut parler de cet ordre d'écriture, on parle de '''boutisme''' (''endianness''). Dans ce qui suit, nous allons partir du principe que le byte fait un octet, mais gardez dans un coin de votre tête que ce n'a pas toujours été le cas. Les explications qui vont suivre restent valide peu importe la taille du byte. ===Les différents types de boutisme=== Les deux types de boutisme les plus simples sont le gros-boutisme et le petit-boutisme. Sur les '''processeurs gros-boutistes''', la donnée est stockée des adresses les plus faibles vers les adresses plus grande. Pour rendre cela plus clair, prenons un entier qui prend plusieurs octets et qui est stocké entre deux adresses. L'octet de poids fort de l'entier est stocké dans l'adresse la plus faible, et inversement pour le poids faible qui est stocké dans l'adresse la plus grande. Sur les '''processeurs petit-boutistes''', c'est l'inverse : l'octet de poids faible de notre donnée est stocké dans la case mémoire ayant l'adresse la plus faible. La donnée est donc stockée dans l'ordre inverse pour les octets. Certains processeurs sont un peu plus souples : ils laissent le choix du boutisme. Sur ces processeurs, on peut configurer le boutisme en modifiant un bit dans un registre du processeur : il faut mettre ce bit à 1 pour du petit-boutiste, et à 0 pour du gros-boutiste, par exemple. Ces processeurs sont dits '''bi-boutistes'''. {| |[[File:Big-Endian-fr.svg|class=transparent|Gros-boutisme.]] |[[File:Little-Endian-fr.svg|class=transparent|Petit-boutisme.]] |} Petit et gros-boutisme ont pour particularité que la taille des mots ne change pas vraiment l'organisation des octets. Peu importe la taille d'un mot, celui-ci se lit toujours de gauche à droite, ou de droite à gauche. Cela n’apparaît pas avec les techniques de boutismes plus compliquées. [[File:Big-endian little-endian.jpg|centre|vignette|upright=2.5|Comparaison entre ''big-endian'' et ''little-endian'', pour des tailles de 16 et 32 bits.]] [[File:Comparaison entre boutisme avec et sans inversion de mots mémoire.jpg|vignette|Comparaison entre un nombre codé en gros-boutiste pur, et un nombre gros-boutiste dont les octets sont rangés dans un groupe en petit-boutiste. Le nombre en question est 0x 0A 0B 0C 0D, en hexadécimal, le premier mot mémoire étant indiqué en jaune, le second en blanc.]] Certains processeurs ont des boutismes plus compliqués, où chaque mot mémoire est découpé en plusieurs groupes d'octets. Il faut alors prendre en compte le boutisme des octets dans le groupe, mais aussi le boutisme des groupes eux-mêmes. On distingue ainsi un boutisme inter-groupe (le boutisme des groupes eux-même) et un boutisme intra-groupe (l'ordre des octets dans chaque groupe), tout deux pouvant être gros-boutiste ou petit-boutiste. Si l'ordre intra-groupe est identique à l'ordre inter-groupe, alors on retrouve du gros- ou petit-boutiste normal. Mais les choses changent si jamais l'ordre inter-groupe et intra-groupe sont différents. Dans ces conditions, on doit préciser un ordre d’inversion des mots mémoire (''byte-swap''), qui précise si les octets doivent être inversés dans un mot mémoire processeur, en plus de préciser si l'ordre des mots mémoire est petit- ou gros-boutiste. ===Avantages, inconvénients et usage=== Le choix entre petit boutisme et gros boutisme est généralement une simple affaire de convention. Il n'y a pas d'avantage vraiment probant pour l'une ou l'autre de ces deux méthodes, juste quelques avantages ou inconvénients mineurs. Dans les faits, il y a autant d'architectures petit- que de gros-boutistes, la plupart des architectures récentes étant bi-boutistes. Précisons que le jeu d'instruction x86 est de type petit-boutiste. Si on quitte le domaine des jeu d'instruction, les protocoles réseaux et les formats de fichiers imposent un boutisme particulier. Les protocoles réseaux actuels (TCP-IP) sont de type gros-boutiste, ce qui impose de convertir les données réseaux avant de les utiliser sur les PC modernes. Et au passage, si le gros-boutisme est utilisé dans les protocoles réseau, alors que le petit-boutisme est roi sur le x86, c'est pour des raisons pratiques, que nous allons aborder ci-dessous. Le gros-boutisme est très facile à lire pour les humains. Les nombres en gros-boutistes se lisent de droite à gauche, comme il est d'usage dans les langues indo-européennes, alors que les nombres en petit boutistes se lisent dans l'ordre inverse de lecture. Pour la lecture en hexadécimal, il faut inverser l'ordre des octets, mais il faut garder l'ordre des chiffres dans chaque octet. Par exemple, le nombre 0x015665 (87 653 en décimal) se lit 0x015665 en gros-boutiste, mais 0x655601 en petit-boutiste. Et je ne vous raconte pas ce que cela donne avec un ''byte-swap''... Cette différence pose problème quand on doit lire des fichiers, du code machine ou des paquets réseau, avec un éditeur hexadécimal. Alors certes, la plupart des professionnels lisent directement les données en passant par des outils d'analyse qui se chargent d'afficher les nombres en gros-boutiste, voire en décimal. Un professionnel a à sa disposition du désassembleur pour le code machine, des analyseurs de paquets pour les paquets réseau, des décodeurs de fichiers pour les fichiers, des analyseurs de ''dump'' mémoire pour l'analyse de la mémoire, etc. Cependant, le gros-boutisme reste un avantage quand on utilise un éditeur hexadécimal, quel que soit l'usage. En conséquence, le gros-boutiste a été historiquement pas mal utilisé dans les protocoles réseaux et les formats de fichiers. Par contre, cet avantage de lecture a dû faire face à divers désavantages pour les architectures de processeur. Le petit-boutisme peut avoir des avantages sur les architectures qui gèrent des données de taille intermédiaires entre le byte et le mot. C'est le cas sur le x86, où l'on peut décider de lire des données de 8, 16, 32, ou 64 bits à partir d'une adresse mémoire. Avec le petit-boutisme, on s'assure qu'une lecture charge bien la même valeur, le même nombre. Par exemple, imaginons que je stocke le nombre 0x 14 25 36 48 sur un mot mémoire, en petit-boutiste. En petit-boutiste, une opération de lecture reverra soit les 8 bits de poids faible (0x 48), soit les 16 bits de poids faible (0x 36 48), soit le nombre complet. Ce ne serait pas le cas en gros-boutiste, où les lectures reverraient respectivement 0x 14, 0x 14 25 et 0x 14 25 36 48. Avec le gros-boutisme, de telles opérations de lecture n'ont pas vraiment de sens. En soit, cet avantage est assez limité et n'est utile que pour les compilateurs et les programmeurs en assembleur. Un autre avantage est un gain de performance pour certaines opérations. Les instructions en question sont les opérations où on doit additionner d'opérandes codées sur plusieurs octets; sur un processeur qui fait les calculs octet par octet. En clair, le processeur dispose d'instructions de calcul qui additionnent des nombres de 16, 32 ou 64 bit, voire plus. Mais à l'intérieur du processeur, les calculs sont faits octets par octets, l'unité de calcul ne pouvant qu'additionner deux nombres de 8 bits à la fois. Dans ce cas, le petit-boutisme garantit que l'addition des octets se fait dans le bon ordre, en commençant par les octets de poids faible pour progresser vers les octets de poids fort. En gros-boutisme, les choses sont beaucoup plus compliquées... Pour résumer, les avantages et inconvénients de chaque boutisme sont mineurs. Le gain en performance est nul sur les architectures modernes, qui ont des unités de calcul capables de faire des additions multi-octets. L'usage d'opérations de lecture de taille variable est aujourd'hui tombé en désuétude, vu que cela ne sert pas à grand chose et complexifie le jeu d'instruction. Enfin, l'avantage de lecture n'est utile que dans situations tellement rares qu'on peut légitimement questionner son statut d'avantage. En bref, les différentes formes de boutisme se valent. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les registres du processeur | prevText=Les registres du processeur | next=Les modes d'adressage | nextText=Les modes d'adressage }} </noinclude> fd3xup42xt4sy67ehpw9arbqhh5og6p Fonctionnement d'un ordinateur/Les circuits de sélection 0 70056 768490 768011 2026-06-24T16:01:49Z Mewtow 31375 /* Le démultiplexeur à deux sorties */ 768490 wikitext text/x-wiki Dans les chapitres précédents, nous avons vu comment fabriquer des circuits relativement généraux. Il est maintenant temps de voir quelques circuits relativement simples, très utilisés. Ces circuits simples sont utilisés pour construire des circuits plus complexes, comme des processeurs, des mémoires, et bien d'autres. Les prochains chapitres vont se concentrer exclusivement sur ces circuits simples, mais courants. Nous allons donner quelques exemples de circuits assez fréquents dans un ordinateur et voir comment construire ceux-ci avec des portes logiques. Dans ce chapitre, nous allons nous concentrer sur quelques circuits, que j'ai décidé de regrouper sous le nom de '''circuits de sélection'''. Les circuits que nous allons présenter sont utilisés dans les mémoires, ainsi que dans certains circuits de calcul. Il est important de bien mémoriser ces circuits, ainsi que la procédure pour les concevoir : nous en aurons besoin dans la suite du cours. Ils sont au nombre de quatre : le décodeur, l'encodeur, le multiplexeur et le démultiplexeur. ==Le décodeur== [[File:DECODER 3 vers 8.png|vignette|Décodeur à 3 entrées et 8 sorties.]] Le premier circuit que nous allons voir est le '''décodeur''', un composant qui contient un grand nombre d'entrées et de sorties, avec des sorties qui sont numérotées. Un décodeur possède une entrée sur laquelle on envoie un nombre codé <math>N</math> bits et <math>2^N</math> sorties de 1 bit. Par exemple, un décodeur avec une entrée de 2 bits aura 4 sorties, un décodeur avec une entrée de 3 bits aura 8 sorties, un décodeur avec une entrée de 8 bits aura 256 sorties, etc. Généralement, on précise le nombre de bits d'entrée et de sortie comme suit : on parle d'un décodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 3 vers 8 pour un décodeur à 3 bits d'entrée et 8 de sortie, de décodeur 4 vers 16, etc. Le fonctionnement d'un décodeur est très simple : il prend sur son entrée un nombre entier x codé en binaire, puis il positionne à 1 la sortie numérotée x et met à zéro toutes les autres sorties. Par exemple, si on envoie la valeur 6 sur ses entrées, il mettra la sortie numéro 6 à 1 et les autres à zéro. Pour résumer, un décodeur est un circuit : * avec une entrée de <math>N</math> bits ; * avec <math>2^N</math> sorties de 1 bit ; * où les sorties sont numérotées en partant de zéro ; * où on ne peut sélectionner qu'une seule sortie à la fois : une seule sortie devra être placée à 1, et toutes les autres à zéro ; * et où deux nombres d'entrée différents devront sélectionner des sorties différentes : la sortie de notre contrôleur qui sera mise à 1 sera différente pour deux nombres différents placés sur son entrée. Une autre manière d'expliquer leur fonctionnement est qu'il traduisent un nombre encodé en binaire vers la représentation ''one-hot''. Pour rappel, sur cette dernière, le nombre N est encodé en mettant le énième bit à 1, les autres sont à 0. Le bit de poids faible compte pour le zéro. Les décodeurs sont très utilisés, au point que faire la liste de leurs utilisations serait bien trop long. Par contre, on peut d'or et déjà prévenir que les décodeurs sont utilisés dans toutes les mémoires RAM et ROM, présentes dans tout ordinateur. La RAM de votre ordinateur contient un ou plusieurs décodeurs, idem pour la mémoire caché intégrée dans le processeur, etc. C'est donc un circuit absolument primordial à étudier, qui reviendra souvent dans ce cours. ===La table de vérité d'un décodeur=== Au vu de ce qui vient d'être dit, on peut facilement écrire la table de vérité d'un décodeur. Pour l'exemple, prenons un décodeur 2 vers 4, pour simplifier la table de vérité. Voici sa table de vérité complète, c’est-à-dire qui contient toutes les sorties regroupées : {|class="wikitable" |- ! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || || 1 || 0 || 0 || 0 |- | 0 || 1 || || 0 || 1 || 0 || 0 |- | 1 || 0 || || 0 || 0 || 1 || 0 |- | 1 || 1 || || 0 || 0 || 0 || 1 |} Vous remarquerez que la table de vérité est assez spéciale. Les seuls bits à 1 sont sur la diagonale. Et cela ne vaut pas que dans l'exemple choisit, mais cela se généralise pour tous les décodeurs. Sur chaque ligne, il n'y a qu'un seul bit à 1, ce qui traduit le fait qu'une entrée ne met qu'une seule sortie est à 1 et met les autres à 0. Si on traduit la table de vérité sous la forme d'équations logiques et de circuit, on obtient ceci : [[File:Decoder Example.svg|centre|vignette|upright=2|Equations logiques et circuit d'un décodeur 2 vers 4.]] Il y a des choses intéressantes à remarquer sur les équations logiques. Pour rappel, l'équation logique d'une sortie est composée, dans le cas général, soit d'un minterm unique, soit d'un OU entre plusieurs minterms. Chaque minterm est l'équation d'un circuit qui compare l'entrée à un nombre bien précis et dépendant du minterm. Si on regarde bien, l'équation de chaque sortie correspond à un minterm et à rien d'autre, il n'y a pas de OU entre plusieurs minterms. Les minterms sont de plus différents pour chaque sortie et on ne trouve pas deux sorties avec le même minterm. Enfin, chaque minterm possible est présent : X bits d'entrée nous donnent 2^X entrées différentes possibles, donc 2^X minterms possibles. Et il se trouve que tous ces minterms possibles sont représentés dans un décodeur, ils ont tous leur sortie associée. C'est une autre manière de définir un décodeur : toutes ses sorties codent un minterm, deux sorties différentes ont des minterms différents et tous les minterms possibles sur n bits sont représentés. Ces informations vont nous être utiles pour la suite. En effet, grâce à elles, nous allons en déduire une méthode générale pour fabriquer un décodeur, peu importe son nombre de bits d'entrée et de sortie. Mais elles permettent aussi de montrer que l'on peut créer n'importe quel circuit combinatoire quelconque à partir d'un décodeur et de quelques portes logiques. Dans ce qui suit, on suppose que le circuit combinatoire en question a une entrée de n bits et une seule sortie de 1 bit. Pour rappel, ce genre de circuit se conçoit en utilisant une table de vérité qu'on traduit en équations logiques, puis en circuits. Le circuit obtenu est alors soit un simple minterm, soit un OU entre plusieurs minterms. Or, le décodeur contient tous les minterms possibles pour une entrée de n bits, avec un minterm par sortie. Il suffit donc de prendre une porte OU et de la connecter aux minterms/sorties adéquats. [[File:Conception d'un circuit combinatoire quelconque à partir d'un décodeur.jpg|centre|vignette|upright=2|Conception d'un circuit combinatoire quelconque à partir d'un décodeur.]] Fabriquer un circuit combinatoire avec un décodeur gaspille pas mal de portes logiques. En effet, le décodeur fournit tous les minterms possibles, alors que seule une minorité est réellement utilisée pour fabriquer le circuit combinatoire. Les minterms en trop correspondent à des paquets de portes NON et ET reliées entre elles, qui ne servent à rien. De plus, les minterms ne sont pas simplifiés. On ne peut pas utiliser les techniques vues dans les chapitres précédents pour simplifier les minterms et réduire le nombre de portes logiques utilisées. Le décodeur reste tel qu'il est, avec l'ensemble des minterms non-simplifiés. Mais la simplicité de conception du circuit reste un avantage dans certaines situations. Notamment, les circuits avec plusieurs bits de sortie sont faciles à fabriquer, notamment si les sorties partagent des minterms (si un minterm est présent dans l'équation de plusieurs sorties différentes, l'usage d'un décodeur permet de facilement factoriser celui-ci). Ceci étant dit, passons à la conception d'un décodeur avec des portes logiques. ===L'intérieur d'un décodeur=== On vient de voir que chaque sortie d'un décodeur correspond à son propre minterm, et que tous les minterms possibles sont représentés. Rappelons que chaque minterm est associé à un circuit qui compare l'entrée à une constante X, X dépendant du minterm. En combinant ces deux informations, on devine qu'un décodeur est simplement composé de comparateurs avec une constante que de minterms/sorties. Par exemple, si je prends un décodeur 7 vers 128, cela veut dire qu'on peut envoyer en entrée un nombre codé entre 0 et 127 et que chaque nombre aura son propre minterm associé : il y aura un minterm qui vérifie si l'entrée vaut 0, un autre vérifie si elle vaut 1, un autre qui vérifie si elle vaut 2, ... , un minterm qui vérifie si l'entrée vaut 126, et enfin un minterm qui vérifie si l'entrée vaut 127. Pour reformuler d'une manière bien plus simple, on peut voir les choses comme suit. Si l'entrée du décodeur vaut N, la sortie mise à 1 est la sortie N. Bref, déduire quand mettre à 1 la sortie N est facile : il suffit de comparer l'entrée avec N. Si l'adresse vaut N, on envoie un 1 sur la sortie, et on envoie un zéro sinon. Pour cela, j'ai donc besoin d'un comparateur pour chaque sortie, et le tour est joué. Précisons cependant que cette méthode gaspille beaucoup de circuits et qu'il y a une certaine redondance. En effet, les comparateurs ont souvent des portions de circuits qui sont identiques et ne diffèrent parfois que ce quelques portes logiques. En utilisant des comparateurs séparés, ces portions de circuits sont dupliquées, alors qu'il serait judicieux de partager. [[File:Internals of decoder.png|centre|vignette|upright=1.5|Exemple d'un décodeur à 8 sorties.]] Comme autre méthode, plus économe en circuits, on peut créer un décodeur en assemblant plusieurs décodeurs plus simples, nommés sous-décodeurs. Ces sous-décodeurs sont des décodeurs normaux, auxquels on a ajouté une entrée RAZ, qui permet de mettre à zéro toutes les sorties : si on met un 0 sur cette entrée, toutes les sorties passent à 0, alors que le décodeur fonctionne normalement sinon. Construire un décodeur demande suffisamment de sous-décodeurs pour combler toutes les sorties. Si on utilise des sous-décodeurs à n entrées, ceux-ci prendront en entrée les n bits de poids faible de l'entrée du décodeur que l'on souhaite construire (le décodeur final). Dans ces conditions, les n décodeurs auront une de leurs sorties à 1. Pour que le décodeur final se comporte comme il faut, il faut désactiver tous les sous-décodeurs, sauf un avec l'entrée RAZ. Pour commander les n bits RAZ des sous-décodeurs, il suffit d'utiliser un décodeur qui est commandé par les bits de poids fort du décodeur final. [[File:Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.jpg|centre|vignette|upright=1.5|Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.]] ==Le démultiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Un démultiplexeur a plusieurs sorties et une seule entrée. Les sorties sont numérotées de 0 à la valeur maximale. Il permet de sélectionner une sortie et de recopier l'entrée dessus, les autres sorties sont mises à 0. Pour séléctionner la sortie, le démultiplexeur possède une entrée de commande, sur laquelle on envoie le numéro de la sortie de destination. Comme le nom l'indique, le démultiplexeur fait l'exact inverse du multiplexeur, que nous verrons plus bas. ===Le démultiplexeur à deux sorties=== Le démultiplexeur le plus simple est le démultiplexeur à deux sorties. Il possède une entrée de donnée, une entrée de commande et deux sorties, toutes de 1 bit. Suivant la valeur du bit sur l'entrée de commande, il recopie le bit d'entrée, soit sur la première sortie, soit sur la seconde. Les deux sorties sont numérotées respectivement 0 et 1. [[File:Demultiplexer.png|centre|vignette|upright=1.5|Démultiplexeur à 2 sorties.]] On peut le concevoir facilement en partant de sa table de vérité. {|class="wikitable" |- ! Entrée de commande ''Select'' ! Entrée de donnée ''Input'' ! ! Sortie 1 ! Sortie 0 |- | 0 || 0 || || 0 || 0 |- |- | 0 || 1 || || 0 || 1 |- |- | 1 || 0 || || 0 || 0 |- |- | 1 || 1 || || 1 || 0 |- |} ===Les démultiplexeurs à plus de deux sorties=== Il est parfaitement possible de créer des démultiplexeurs en utilisant les méthodes du chapitre sur les circuits combinatoires, comme ma méthode des ''minterms'' ou les tableaux de Karnaugh. On obtient alors un démultiplexeur assez simple, composé de deux couches de portes logiques : une couche de portes NON et une couche de portes ET à plusieurs entrées. [[File:Demux.PNG|centre|vignette|upright=1.5|Démultiplexeur fabriqué avec une table de vérité.]] Mais cette méthode n'est pas pratique, car elle utilise beaucoup de portes logiques et que les portes logiques avec beaucoup d'entrées sont difficiles à fabriquer. Pour contourner ces problèmes, il est possible de créer des démultiplexeurs en assemblant des démultiplexeurs 1 vers 2. Évidemment, le même principe s'applique à des démultiplexeurs plus complexes : il suffit de rajouter des couches. [[File:Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.jpg|centre|vignette|upright=1.5|Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.]] Un démultiplexeur peut aussi se fabriquer en utilisant un décodeur et quelques portes ET. Pour comprendre pourquoi, regardons la table de vérité d'un démultiplexeur à quatre sorties. Si vous éliminez le cas où l'entrée de donnée ''Input'' vaut 0, et que vous tenez compte uniquement des entrées de commande, vous retombez sur la table de vérité d'un décodeur. Cela correspond aux cases en rouge. {|class="wikitable" |- ! Input !! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 0 || 1 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 1 || || 0 || 0 || 0 || 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |} En réalité, Le fonctionnement d'un démultiplexeur peut se résumer comme suit : soit l'entrée ''Input'' est à 1 et il fonctionne comme un décodeur dont l'entrée est l'entrée de commande, soit l'entrée ''Input'' vaut 0 et sa sortie est mise à 0. On devine donc qu'il faut combiner un décodeur avec le circuit de mise à zéro vu dans le chapitre précédent. On devine rapidement que l'entrée ''Input'' commande la mise à zéro de la sortie, ce qui donne le circuit suivant : [[File:Démultiplexeur conçu à partir d'un décodeur.jpg|centre|vignette|upright=1.5|Démultiplexeur conçu à partir d'un décodeur.]] ==Le multiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Les multiplexeurs sont des composants qui possèdent un nombre variable d'entrées, mais une seule sortie. Un multiplexeur permet de sélectionner une entrée et de recopier son contenu sur sa sortie, les entrées non-sélectionnées étant ignorées. Sélectionner l'entrée à recopier sur la sortie se fait en configurant une entrée de commande du multiplexeur. Les entrées sont numérotées de 0 à la valeur maximale. Configurer l'entrée de commande demande juste d'envoyer le numéro de l'entrée sélectionnée dessus. [[File:4-to-1 multiplexer.svg|centre|vignette|Multiplexeur à 4 entrées.]] Les multiplexeurs sont très utilisés et on en retrouve partout : dans les mémoires RAM, dans les processeurs, dans les circuits de calcul, dans les circuits pour communiquer avec les périphériques, et j'en passe. Il s'agit d'un composant très utilisé, qu'il est primordial de bien comprendre avant de passer à la suite du cours. ===Le multiplexeur à deux entrées=== Le multiplexeur le plus simple est le multiplexeur à deux entrées et une sortie. Il est facile de le construire avec des portes logiques, dans les implémentations les plus simples. Sachez toutefois que les multiplexeurs utilisés dans les ordinateurs récents ne sont pas forcément fabriqués avec des portes logiques, mais qu'on peut aussi les fabriquer directement avec des transistors. [[File:Multiplexeur à deux entrées - symbole.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - symbole.]] Pour commencer, établissons sa table de vérité. On va supposer qu'un 0 sur l'entrée de commande sélectionne l'entrée a. La table de vérité devrait être la suivante : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||0||0||0 |- |0||0||1||0 |- |0||1||0||1 |- |0||1||1||1 |- |1||0||0||0 |- |1||0||1||1 |- |1||1||0||0 |- |1||1||1||1 |} Sélectionnons les lignes qui mettent la sortie à 1 : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||1||0||1 |- |0||1||1||1 |- |1||0||1||1 |- |1||1||1||1 |} On sait maintenant quels comparateurs avec une constante utiliser. On peut, écrire l'équation logique du circuit. La première ligne donne l'équation suivante : <math>\overline{E_c} . a . \overline{b}</math>, la seconde donne l'équation <math>\overline{E_c} . a . b</math> , la troisième l'équation <math>E_c . \overline{a} . b</math> et la quatrième l'équation <math>E_c . a . b</math>. L'équation finale obtenue est donc : : <math>(\overline{E_c} . a . \overline{b}) + (\overline{E_c} . a . b) + (E_c . \overline{a} . b) + E_c . a . b</math> L'équation précédente est assez compliquée, mais il y a moyen de la simplifier assez radicalement. Pour cela, nous allons utiliser les règles de l’algèbre de Boole. Pour commencer, nous allons factoriser <math>(\overline{E_c}</math> et <math>E_c</math> : : <math> \left[ \overline{E_c} .[ (a . \overline{b}) + (a . b)] \right] + \left[ E_c . [(\overline{a} . b) + (a . b)] \right] </math> Ensuite, factorisons <math>a</math> dans le premier terme et <math>b</math> dans le second : : <math> \left[ \overline{E_c} . a . (\overline{b} + b) \right] + \left[ E_c . b . (\overline{a} + a) \right]</math> Les termes <math>\overline{b} + b</math> et <math>\overline{a} + a</math> valent 1 : : <math> \left[ \overline{E_c} . a . 1 \right] + \left[ E_c . b . 1 \right]</math> On sait que <math>a . 1 = a</math>, ce qui fait que l'équation simplifiée est la suivante : : <math>(\overline{E_c} . a) + (E_c . b)</math> Le circuit qui correspond est : [[File:Multiplexeur à deux entrées - circuit.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - circuit.]] ===Les multiplexeurs à plus de deux entrées=== Il est possible de concevoir un multiplexeur quelconque à partir de sa table de vérité. Le résultat est alors un circuit composé d'une porte OU à plusieurs entrées, de plusieurs portes ET, et de quelques portes NON. Un exemple est illustré ci-dessous. Vous remarquerez cependant que ce circuit a un défaut : la porte OU finale a beaucoup d'entrées, ce qui pose de nombreux problèmes techniques. Il est difficile de concevoir des portes logiques avec un très grand nombre d'entrées. Aussi, les applications à haute performance demandent d'utiliser d'autres solutions. [[File:Mux2.png|centre|vignette|upright=1.5|Multiplexeur conçu à partir de sa table de vérité.]] Une solution alternative conçoit un multiplexeur à plus de deux entrées en combinant des multiplexeurs plus simples. Par exemple, en prenant deux multiplexeurs plus simples, et en ajoutant un multiplexeur 2 vers 1 sur leurs sorties respectives. Le multiplexeur final se contente de sélectionner une sortie parmi les deux sorties des multiplexeurs précédents, qui ont déjà effectué une sorte de présélection. [[File:Multiplexeur conçu à partir de multiplexeurs plus simples.jpg|centre|vignette|upright=1.5|Multiplexeur conçu à partir de multiplexeurs plus simples.]] Il existe toutefois une manière bien plus simple pour créer un multiplexeur, qui utilise des opérations de masquage. L'idée est qu'un multiplexeur sélectionne un bit bien précis dans l'opérande. L'idée est de masquer les bits non-sélectionnés, puis de regarder le résultat après masquage. Par défaut, les bits non-sélectionnés sont mis à 0. Le résultat après masquage dépend de la valeur du bit sélectionné : * Si le bit sélectionné vaut 0, alors tous les bits après masquage sont à zéro. * Si le bit sélectionné vaut 1, alors seul un bit du résultat après masquage est à 1. Pour savoir si au moins un bit du résultat vaut 1, l'idée est d'utiliser une porte OU. Si tous les bits sont à 0, la porte OU donnera un zéro. Sinon, elle sortira un 1. La sortie du multiplexeur s'obtient donc en faisant un OU logique entre tous les bits du résultat après masquage. Le circuit au complet est donc composé d'un circuit de masquage, d'un circuit qui génère le masque, et d'une porte OU. Le circuit qui génère le masque transforme le numéro du bit en un masque adéquat. Si le numéro du bit est de N, le masque a son énième bit à 1, les autres à 0. Pour le dire autrement, il convertit le numéro du bit en sa représentation ''one-hot''. Et ce n'est ni plus ni moins que ce que fait un décodeur ! La génération du masque est donc le fait d'un décodeur. [[File:Multiplexeur 2 vers 4 conçu à partir d'un décodeur.png|centre|vignette|upright=2|Multiplexeur 2 vers 4 conçu à partir d'un décodeur]] ==L'encodeur== [[File:8 to 3 simple encoder IEC symbol.svg|vignette|upright=0.5|Encodeur à 8 entrées (et 3 sorties).]] Il existe un circuit qui fait exactement l'inverse du décodeur : c'est l''''encodeur'''. Là où les décodeurs ont une entrée de <math>N</math> bits et <math>2^N</math> sorties de 1 bit, l'encodeur a à l'inverse <math>2^N</math> entrées de 1 bit avec une sortie de <math>N</math> bits. Par exemple, un encodeur avec une entrée de 4 bits aura 2 sorties, un décodeur avec une entrée de 8 bits aura 3 sorties, un décodeur avec une entrée de 256 bits aura 8 sorties, etc. Comme pour les décodeurs, on parle d'un encodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 8 vers 3 pour un décodeur à 8 bits d'entrée et 3 de sortie, de décodeur 16 vers 4, etc. [[File:Encoder block diagram.jpg|centre|vignette|upright=1.5|Entrées et sorties d'un encodeur.]] De plus, contrairement au décodeur, ce sont les entrées qui sont numérotées de 0 à N et non les sorties. Dans ce qui suit, on va supposer qu'une seule des entrées est à 1. Il existe des encodeurs capables de traiter le cas où plusieurs bits d'entrée sont à 1, qui sont appelés des encodeurs à priorité, mais nous les laissons pour le chapitre suivant. Le chapitre suivant sera totalement dédié aux encodeurs à priorité, aussi nous préférons nous focaliser sur le cas d'un encodeur simple, capable de traiter uniquement le cas où une seule entrée est à 1. En sortie, l'encodeur donne le numéro de l'entrée qui est à 1. Par exemple, si l'entrée numéro 5 est à 1 et les autres à 0, alors l'encodeur envoie un 5 sur sa sortie. Une autre manière d'expliquer son fonctionnement est la suivant : un encodeur traduit un nombre codé en représentation ''one-hot'' vers du binaire normal. L'utilité d'un encodeur n'est pas très évidente à ce moment du cours, mais nous pouvons déjà dire qu'ils seront utiles dans certaines formes de mémoires RAM appelées des mémoires associatives, qui sont utilisées dans des routeurs, switchs et autre matériel réseau. La majorité des mémoires caches de nos ordinateurs sont de ce type, bien que leur implémentation exacte ne fasse pas usage d'un encodeur. Une autre utilisation est la transformation d'un nombre codé en représentation ''one-hot'' vers du binaire normal, chose marginalement utile. ===L'encodeur 4 vers 2=== Prenons l'exemple d'un encodeur à 4 entrées et 2 sorties. Écrivons sa table de vérité. D'après la description du circuit, on devrait trouver ceci : {|class="wikitable" |+ Table de vérité d'un encodeur 4 vers 2 |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 |- | 0 || 0 || 0 || 1 || || 0 || 0 |- | 0 || 0 || 1 || 0 || || 0 || 1 |- | 0 || 1 || 0 || 0 || || 1 || 0 |- | 1 || 0 || 0 || 0 || || 1 || 1 |} Vous voyez que la table de vérité est incomplète. En effet, l'encodeur fonctionne tant qu'une seule de ses entrées est à 1. L'encodeur dit alors quelle est la sortie à 1, mais cela suppose que les autres soient à 0. Si plusieurs entrées sont à 1, le comportement de l'encodeur est potentiellement erroné. En effet, il donnera un résultat incorrect sur certaines entrées. Mais passons cela sous silence et ne tenons compte que de la table de vérité partielle précédente. On peut traduire cette table de vérité en circuit logique. On obtient alors les équations suivantes : : <math>S1 = E3 + E2</math> : <math>S0 = E3 + E1</math> Le tout donne le circuit suivant : [[File:A Simple 4-2 encoder using or gate.jpg|centre|vignette|upright=1.5|Exemple d'encodeur à 4 entrées et 2 sorties.]] ===Les encodeurs à plus de deux sorties=== Il est possible de créer un encodeur complexe en combinant plusieurs encodeurs simples. C'est un peu la même chose qu'avec les décodeurs, pour lesquels on peut créer un décodeur 8 vers 256 à base de deux décodeurs 7 vers 128, ou de quatre décodeurs 6 vers 64. L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Pour comprendre l'idée, prenons la table de vérité d'un encodeur 8 vers 3; donnée dans le tableau ci-dessous. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 0 || 1 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 1 || 0 || || 0 || 0 || 1 |- | 0 || 0 || 0 || 0 || 0 || 1 || 0 || 0 || || 0 || 1 || 0 |- | 0 || 0 || 0 || 0 || 1 || 0 || 0 || 0 || || 0 || 1 || 1 |- | 0 || 0 || 0 || 1 || 0 || 0 || 0 || 0 || || 1 || 0 || 0 |- | 0 || 0 || 1 || 0 || 0 || 0 || 0 || 0 || || 1 || 0 || 1 |- | 0 || 1 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 0 |- | 1 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 1 |} En regardant bien, vous verrez que vous pouvez trouver la table de vérité d'un encodeur 4 vers 2 en deux exemplaires, indiquées en rouge. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |} On voit que les deux bits de poids faibles correspondent à la sortie de l'encodeur activé par l'entrée. Si le premier encodeur est activé, c'est lui qui fournit les bits de poids faibles. Inversement, si c'est le second encodeur qui a un résultat non-nul, c'est lui qui fournit les bits de poids faible. Notons que seul un des deux encodeurs a une sortie non-nulle à la fois : soit le premier a une sortie non-nulle, soit c'est le second, mais c'est impossible que ce soit les deux en même temps. Cela permet de déduire quelle opération permet de mixer les deux résultats : un simple OU logique suffit. Car, pour rappel, 0 OU X donne X, quelque que soit le X en question. Les bits de poids faible du résultat se calculent en faisant un OU entre les deux résultats des encodeurs. Ensuite, il faut déterminer comment fixer le bit de poids fort du résultat. Il vaut 0 si le premier encodeur a une entrée non-nulle, et 1 si c'est le premier encodeur qui a une entrée non-nulle. Pour cela, il suffit de vérifier si les bits de poids forts, associés au premier encodeur, contiennent un 1. Si c'est le cas, alors on met la troisième sortie à 1. [[File:Encodeur fabriqué à partir d'encodeurs plus petits.png|centre|vignette|upright=2|Encodeur fabriqué à partir d'encodeurs plus petits.]] Notons que cette procédure, à savoir faire un OU entre les sorties de deux encodeurs simples, puis faire un OU pour calculer le troisième bit, marche pour tout encodeur de taille quelconque. À vrai dire, le circuit obtenu plus haut d'un encodeur 4 vers 2 est conçu ainsi, mais en combinant deux encodeurs 2 vers 1. La procédure consiste à ajouter trois portes OU à deux encodeurs. Mais ceux-ci sont eux-même composés de portes OU associées à des encodeurs plus petits, et ainsi de suite. On peut poursuivre ainsi jusqu’à tomber sur des encodeurs 4 vers 2, qui sont eux-mêmes composés de deux portes OU. Au final, on se retrouve avec un circuit conçu uniquement à partir de portes OU. Notons qu'il est possible de simplifier le circuit obtenu avec la procédure en fusionnant des portes OU. Si on simplifie vraiment au maximum, le circuit consiste alors en une porte OU à plusieurs entrées par sortie, chacune étant connectée à certaines entrées bien précises. Pour un encodeur 8 vers 3, la simplification du circuit devrait donner ceci : [[File:8-3 Encoder.gif|centre|vignette|upright=1.5|Encodeur 8 vers 3.]] ==L'encodeur à priorité== L''''encodeur à priorité''' est un dérivé du circuit encodeur, vu dans la section précédente. La différence ne se situe pas dans le nombre d'entrée ou de sortie, ni même dans son interface extérieure. Comme pour l'encodeur normal, l'encodeur à priorité possède <math>2^N</math> entrées numérotées de 0 à <math>2^N - 1</math> et N sorties. Une autre manière plus intuitive de le dire est qu'il possède N entrées et <math>\log_2{N}</math> sorties. Pas de changement de ce point de vue. La différence entre encodeur simple et encodeur à priorité tient dans leur fonctionnement, dans le calcul qu'ils font. Avec un encodeur normal, on a supposé que seul un bit d'entrée pouvait être à 1, les autres étant systématiquement à 0. Si cette condition est naturellement remplie dans certains cas d’utilisation, ce n'est pas le cas dans d'autres. L'encodeur à priorité est un encodeur amélioré dans le sens où il donne un résultat valide même quand plusieurs bits d'entrée sont à 1. Il donne donc un résultat pour n'importe quel nombre passé en entrée. Mais avant de passer aux explications, un peu de terminologie utile. Dans ce qui suit, nous aurons à utiliser des expressions du type "le 1 de poids faible", "le 1 de poids fort" et quelques autres du même genre. Quand nous parlerons du 1 de poids faible, nous voudrons parler du premier 1 que l'on croise dans un nombre en partant de sa droite. Par exemple, dans le nombre 0110 1000, le 1 de poids faible est le quatrième bit. Quant au "1 de poids fort", c'est le premier 1 que l'on croise quand on parcourt le nombre à partir de sa gauche. Dans le cas le plus fréquent, l'encodeur à priorité prend en entrée un nombre et donne la position du 1 de poids fort. Mais dans d'autres cas, l'encodeur à priorité donne la position du 1 de poids faible. Il existe des équivalents, mais qui trouvent cette fois-ci les zéros de poids fort/faible, mais nous n'en parlerons pas dans ce chapitre. ===L'encodeur à priorité conçu à partir de sa table de vérité=== Il est possible de concevoir l'encodeur à priorité à partir de sa table de vérité, mais les méthodes des minterms ou des maxterms ne donnent pas de très bons résultats. Notons que ces encodeurs ont souvent une nouvelle entrée notée V, qui indique si la sortie est valide, et qui indique qu'au moins une entrée est à 1. Elle vaut 1 si au moins une entrée est à 1, 0 si toutes les entrées sont à 0. À titre d'exemple, la table de vérité d'un encodeur à priorité 4 vers 2 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. {|class="wikitable" |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 !! V |- | 0 || 0 || 0 || 0 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 1 || || 0 || 0 || 1 |- | 0 || 0 || 1 || X || || 0 || 1 || 1 |- | 0 || 1 || X || X || || 1 || 0 || 1 |- | 1 || X || X || X || || 1 || 1 || 1 |} Les équations logiques obtenues sont donc les suivantes : : <math>V = E3 + E2 + E1 + E0</math> : <math>S0 = E3 + (\overline{E3} . \overline{E2} . E1)</math> : <math>S1 = E3 + ( \overline{E3} . E2 )</math> On voit quelle est la logique de chaque équation. Pour chaque ligne de la table de vérité, il faut vérifier si les bits de poids fort sont à 0, suivi par un 1, les bits de poids faible après le 1 étant oubliées. Pour le bit de validité, il suffit de faire un OU entre toutes les entrées. Les deux dernières équations se simplifient en : : <math>S0 = E3 + (\overline{E2} . E1)</math> : <math>S1 = E3 + E2</math>, Le circuit obtenu est le suivant : [[File:Pr encoder 4x2.png|centre|vignette|upright=1.5|Encodeur à priorité 4 vers 2.]] La table de vérité d'un encodeur à priorité 8 vers 3 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. [[File:Encoder.JPG|centre|vignette|upright=2|Table de vérité d'un encodeur à priorité 8 vers 3.]] Utiliser la table de vérité a des défauts. Premièrement, ce n'est pas la meilleure des solutions pour des circuits avec un grand nombre d'entrée. Faire cela donne des tables de vérité rapidement importantes, mêmes pour des encodeurs avec peu de sorties. Le circuit final utilise beaucoup de portes logiques comparé aux autres méthodes. Les solutions alternatives que nous allons voir dans ce qui suit permettent de résoudre ces deux problèmes en même temps. ===Les encodeurs à priorité récursifs=== Une première solution consiste à créer un gros encodeur à base d'encodeurs plus petits.L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Naturellement, il est préférable d'utiliser plusieurs exemplaires d'un même encodeur, c'est à dire que pour une entrée de 256 bits, il vaut mieux utiliser soit deux décodeurs 7 vers 128, soit quatre décodeurs 6 vers 64, etc. La construction est similaire à celle vue dans le chapitre précédent, dans la section sur les encodeurs. La différence est que le OU entre les sorties des encodeurs est remplacé par un multiplexeur. Une version générale est illustrée ci-dessous. On voit que les encodeurs ont une sortie de résultat de X bits notée idx et une sortie de validité notée vld. La sortie de validité finale se calcule en combinant les sorties de validité de chaque encodeur. La sortie est par définition à 1 tant qu'un seul encodeur a une sortie non-nulle, donc quand un seul encodeur a un bit de validité à 1. En clair, c'est un simple OU entre les bits de validité. Reste à déterminer la sortie de donnée, celle qui donne la position du 1 de poids fort. On peut dire que si l'on utilise des encodeurs avec N bits de sortie, alors les N bits de poids faible du résultat seront donnés par le premier encodeur avec une sortie non-nulle. Les résultats de chaque encodeur donnent doncles X bits de poids faible, un seul résultat devant être sélectionné. Le résultat à sélectionner est le premier à avoir un résultat non-nul, donc à avoir un bit de validité à 1. En clair, on peut déterminer quel est le bon encodeur, le bon résultat, en analysant les bits de validité. Mieux : d'après ce qui a été dit, on peut deviner que l'analyse réalisée correspond à trouver la position du premier encodeur à avoir un bit de validité à 1. En clair, c'est l'opération réalisée par un encodeur à priorité lui-même. Tout cela permet de déterminer les N bits de poids faible, amis les autres bits, ceux de poids fort, sont encore à déterminer. Pour cela, on peut remarquer que ceux-ci sont eux-même fournit par l'encodeur à priorité qui commande le MUX. [[File:PE-recursion.svg|centre|vignette|upright=2|Construction d'un encodeur à priorité à partir d'encodeur à priorité plus petits.]] Notons qu'avec cette méthode, il est possible, mais pas très intuitif, de fabriquer un encodeur configurable, capable de se comporter soit comme un encodeur de type ''Find Highest Set'', soit de type ''Find First Set''. L'implémentation la plus simple demande de modifier le circuit qui combine les résultats pour qu'il soit configurable et puisse faire les deux opérations à la demande. ===L'encodeur à priorité avec un circuit d'isolation du 1 de poids fort/faible=== Une autre solution part d'un encodeur normal, auquel on ajoute un circuit qui se charge de sélectionner un seul des bits passé sur son entrée. Le circuit de gestion des priorités a pour fonction de trouver sélectionner un bit et de mettre les autres 1 à 0. Suivant le circuit de priorité considéré, le bit sélectionné est soit le 1 de poids fort, soit le 1 de poids faible. Dans certains cas, le circuit de priorité est configurable et peut trouver l'un ou l'autre suivant ce qu'on lui demande. Dans ce qui va suivre, nous allons partir du principe que l'on souhaite avoir un encodeur qui trouve le 1 de poids fort, sauf indication contraire. [[File:Encodeur à priorité.png|centre|vignette|upright=2|Encodeur à priorité.]] Une méthode assez pratique découpe le circuit de gestion des priorité en petites briques de bases, reliées les unes à la suite des autres. L'idée est que les briques de base sont connectées de manière à propager un signal de mise à zéro. Si une brique détecte un 1, elle envoie un signal aux briques précédentes/suivantes, qui leur dit de mettre leur sortie à zéro. Ce faisant, une fois le premier 1 trouvé, on est certain que les autres bits précédents/suivants sont mis à zéro. Suivant les connexions des briques de base, on peut obtenir soit un encodeur qui effectue l'opération ''Find First Set'', soit encodeur de type ''Find Highest Set'' et réciproquement. En fait, suivant que les briques soient reliées de droite à gauche ou de gauche à droite, on obtiendra l'un ou l'autre de ces deux encodeurs. [[File:Circuit de gestion des priorités.png|centre|vignette|upright=2|Circuit de gestion des priorités.]] Chaque brique de base peut soit recopier le bit en entrée, soit le mettre à zéro. Pour décider quoi faire, elle regarde le signal d'entrée RAZ (''Remise A Zéro''). Si le bit RAZ vaut 1, la sortie est mise à zéro automatiquement. Dans le cas contraire, le bit passé en entrée est recopié. De plus, chaque brique de base doit fournir un signal de remise à zéro RAZ à destination de la brique suivante. Ce signal RAZ de sortie est mis à 1 dans deux cas : soit si le bit d'entrée vaut, soit quand le signal d'entrée RAZ est à 1. Si vous cherchez à la concevoir à partir d'un table de vérité, vous obtiendrez ceci : {| |[[File:Brique de base du circuit de gestion des priorités d'un encodeur à priorité.png|vignette|Brique de base du circuit de gestion des priorités d'un encodeur à priorité.]] |[[File:Circuit de gestion des priorité - Circuit de la brique de base.png|vignette|upright=1.5|Circuit de gestion des priorité - Circuit de la brique de base.]] |} Le circuit complet d'un encodeur à priorité peut être déduit facilement à partir des raisonnements précédents. Après quelques simplifications, on peut obtenir le circuit suivant. On voit qu'on a ajouté une ligne de briques RAZ à l'encodeur 8 vers 3 vu plus haut. [[File:Koder priorytetowy.jpg|centre|vignette|upright=2|Encodeur à priorités]] Le défaut de cette méthode est que le circuit de gestion des priorité est assez lent. Dans le pire des cas, le signal de remise à zéro traverse toutes les briques de base, soit autant qu'il y a de bits d'entrée. Si chaque brique de base met un certain temps, le temps mis pour que le circuit de priorité fasse son travail est proportionnel au nombre de bits de l'entrée. Cela n'a l'air de rien, mais cela peut prendre un temps rédhibitoire pour les circuits de haute performance, destinés à fonctionner à haute fréquence. Pour ces circuits, on préfère que le temps de calcul soit proportionnel au logarithme du nombre de bits d'entrée, un temps proportionnel étant considéré comme trop lent, surtout pour des opérations simples comme celles étudiées ici. Une version légèrement différente de ce circuit est utilisée dans le processeur ARM1, un des tout premiers processeur ARM. L'encodeur à priorité était bidirectionnel, à savoir capable de déterminer soit la place du 1 de poids faible, soit du 1 de poids fort. Pour ceux qui veulent en savoir plus, et qui ont déjà un bagage solide en architecture des ordinateurs, voici un lien à ce sujet : : [https://www.righto.com/2016/01/more-arm1-processor-reverse-engineering.html More ARM1 processor reverse engineering: the priority encoder ] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les circuits de masquage | prevText=Les circuits de masquage | next=Les circuits incrémenteurs/décrémenteurs | nextText=Les circuits incrémenteurs/décrémenteurs }} </noinclude> 6pjdvmd7u0v5fhu8cqfw998wvgmm5lg 768491 768490 2026-06-24T16:02:06Z Mewtow 31375 /* La table de vérité d'un décodeur */ 768491 wikitext text/x-wiki Dans les chapitres précédents, nous avons vu comment fabriquer des circuits relativement généraux. Il est maintenant temps de voir quelques circuits relativement simples, très utilisés. Ces circuits simples sont utilisés pour construire des circuits plus complexes, comme des processeurs, des mémoires, et bien d'autres. Les prochains chapitres vont se concentrer exclusivement sur ces circuits simples, mais courants. Nous allons donner quelques exemples de circuits assez fréquents dans un ordinateur et voir comment construire ceux-ci avec des portes logiques. Dans ce chapitre, nous allons nous concentrer sur quelques circuits, que j'ai décidé de regrouper sous le nom de '''circuits de sélection'''. Les circuits que nous allons présenter sont utilisés dans les mémoires, ainsi que dans certains circuits de calcul. Il est important de bien mémoriser ces circuits, ainsi que la procédure pour les concevoir : nous en aurons besoin dans la suite du cours. Ils sont au nombre de quatre : le décodeur, l'encodeur, le multiplexeur et le démultiplexeur. ==Le décodeur== [[File:DECODER 3 vers 8.png|vignette|Décodeur à 3 entrées et 8 sorties.]] Le premier circuit que nous allons voir est le '''décodeur''', un composant qui contient un grand nombre d'entrées et de sorties, avec des sorties qui sont numérotées. Un décodeur possède une entrée sur laquelle on envoie un nombre codé <math>N</math> bits et <math>2^N</math> sorties de 1 bit. Par exemple, un décodeur avec une entrée de 2 bits aura 4 sorties, un décodeur avec une entrée de 3 bits aura 8 sorties, un décodeur avec une entrée de 8 bits aura 256 sorties, etc. Généralement, on précise le nombre de bits d'entrée et de sortie comme suit : on parle d'un décodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 3 vers 8 pour un décodeur à 3 bits d'entrée et 8 de sortie, de décodeur 4 vers 16, etc. Le fonctionnement d'un décodeur est très simple : il prend sur son entrée un nombre entier x codé en binaire, puis il positionne à 1 la sortie numérotée x et met à zéro toutes les autres sorties. Par exemple, si on envoie la valeur 6 sur ses entrées, il mettra la sortie numéro 6 à 1 et les autres à zéro. Pour résumer, un décodeur est un circuit : * avec une entrée de <math>N</math> bits ; * avec <math>2^N</math> sorties de 1 bit ; * où les sorties sont numérotées en partant de zéro ; * où on ne peut sélectionner qu'une seule sortie à la fois : une seule sortie devra être placée à 1, et toutes les autres à zéro ; * et où deux nombres d'entrée différents devront sélectionner des sorties différentes : la sortie de notre contrôleur qui sera mise à 1 sera différente pour deux nombres différents placés sur son entrée. Une autre manière d'expliquer leur fonctionnement est qu'il traduisent un nombre encodé en binaire vers la représentation ''one-hot''. Pour rappel, sur cette dernière, le nombre N est encodé en mettant le énième bit à 1, les autres sont à 0. Le bit de poids faible compte pour le zéro. Les décodeurs sont très utilisés, au point que faire la liste de leurs utilisations serait bien trop long. Par contre, on peut d'or et déjà prévenir que les décodeurs sont utilisés dans toutes les mémoires RAM et ROM, présentes dans tout ordinateur. La RAM de votre ordinateur contient un ou plusieurs décodeurs, idem pour la mémoire caché intégrée dans le processeur, etc. C'est donc un circuit absolument primordial à étudier, qui reviendra souvent dans ce cours. ===La table de vérité d'un décodeur=== Au vu de ce qui vient d'être dit, on peut facilement écrire la table de vérité d'un décodeur. Pour l'exemple, prenons un décodeur 2 vers 4, pour simplifier la table de vérité. Voici sa table de vérité complète, c’est-à-dire qui contient toutes les sorties regroupées : {|class="wikitable" |- ! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || || 1 || 0 || 0 || 0 |- | 0 || 1 || || 0 || 1 || 0 || 0 |- | 1 || 0 || || 0 || 0 || 1 || 0 |- | 1 || 1 || || 0 || 0 || 0 || 1 |} Vous remarquerez que la table de vérité est assez spéciale. Les seuls bits à 1 sont sur la diagonale. Et cela ne vaut pas que dans l'exemple choisit, mais cela se généralise pour tous les décodeurs. Sur chaque ligne, il n'y a qu'un seul bit à 1, ce qui traduit le fait qu'une entrée ne met qu'une seule sortie est à 1 et met les autres à 0. Si on traduit la table de vérité sous la forme d'équations logiques et de circuit, on obtient ceci : [[File:Decoder Example.svg|centre|vignette|upright=2|Equations logiques et circuit d'un décodeur 2 vers 4.]] Le circuit obtenu est le suivant : [[File:2to4demux.svg|centre|vignette|upright=2|Démultiplexeur à deux sorties.]] Il y a des choses intéressantes à remarquer sur les équations logiques. Pour rappel, l'équation logique d'une sortie est composée, dans le cas général, soit d'un minterm unique, soit d'un OU entre plusieurs minterms. Chaque minterm est l'équation d'un circuit qui compare l'entrée à un nombre bien précis et dépendant du minterm. Si on regarde bien, l'équation de chaque sortie correspond à un minterm et à rien d'autre, il n'y a pas de OU entre plusieurs minterms. Les minterms sont de plus différents pour chaque sortie et on ne trouve pas deux sorties avec le même minterm. Enfin, chaque minterm possible est présent : X bits d'entrée nous donnent 2^X entrées différentes possibles, donc 2^X minterms possibles. Et il se trouve que tous ces minterms possibles sont représentés dans un décodeur, ils ont tous leur sortie associée. C'est une autre manière de définir un décodeur : toutes ses sorties codent un minterm, deux sorties différentes ont des minterms différents et tous les minterms possibles sur n bits sont représentés. Ces informations vont nous être utiles pour la suite. En effet, grâce à elles, nous allons en déduire une méthode générale pour fabriquer un décodeur, peu importe son nombre de bits d'entrée et de sortie. Mais elles permettent aussi de montrer que l'on peut créer n'importe quel circuit combinatoire quelconque à partir d'un décodeur et de quelques portes logiques. Dans ce qui suit, on suppose que le circuit combinatoire en question a une entrée de n bits et une seule sortie de 1 bit. Pour rappel, ce genre de circuit se conçoit en utilisant une table de vérité qu'on traduit en équations logiques, puis en circuits. Le circuit obtenu est alors soit un simple minterm, soit un OU entre plusieurs minterms. Or, le décodeur contient tous les minterms possibles pour une entrée de n bits, avec un minterm par sortie. Il suffit donc de prendre une porte OU et de la connecter aux minterms/sorties adéquats. [[File:Conception d'un circuit combinatoire quelconque à partir d'un décodeur.jpg|centre|vignette|upright=2|Conception d'un circuit combinatoire quelconque à partir d'un décodeur.]] Fabriquer un circuit combinatoire avec un décodeur gaspille pas mal de portes logiques. En effet, le décodeur fournit tous les minterms possibles, alors que seule une minorité est réellement utilisée pour fabriquer le circuit combinatoire. Les minterms en trop correspondent à des paquets de portes NON et ET reliées entre elles, qui ne servent à rien. De plus, les minterms ne sont pas simplifiés. On ne peut pas utiliser les techniques vues dans les chapitres précédents pour simplifier les minterms et réduire le nombre de portes logiques utilisées. Le décodeur reste tel qu'il est, avec l'ensemble des minterms non-simplifiés. Mais la simplicité de conception du circuit reste un avantage dans certaines situations. Notamment, les circuits avec plusieurs bits de sortie sont faciles à fabriquer, notamment si les sorties partagent des minterms (si un minterm est présent dans l'équation de plusieurs sorties différentes, l'usage d'un décodeur permet de facilement factoriser celui-ci). Ceci étant dit, passons à la conception d'un décodeur avec des portes logiques. ===L'intérieur d'un décodeur=== On vient de voir que chaque sortie d'un décodeur correspond à son propre minterm, et que tous les minterms possibles sont représentés. Rappelons que chaque minterm est associé à un circuit qui compare l'entrée à une constante X, X dépendant du minterm. En combinant ces deux informations, on devine qu'un décodeur est simplement composé de comparateurs avec une constante que de minterms/sorties. Par exemple, si je prends un décodeur 7 vers 128, cela veut dire qu'on peut envoyer en entrée un nombre codé entre 0 et 127 et que chaque nombre aura son propre minterm associé : il y aura un minterm qui vérifie si l'entrée vaut 0, un autre vérifie si elle vaut 1, un autre qui vérifie si elle vaut 2, ... , un minterm qui vérifie si l'entrée vaut 126, et enfin un minterm qui vérifie si l'entrée vaut 127. Pour reformuler d'une manière bien plus simple, on peut voir les choses comme suit. Si l'entrée du décodeur vaut N, la sortie mise à 1 est la sortie N. Bref, déduire quand mettre à 1 la sortie N est facile : il suffit de comparer l'entrée avec N. Si l'adresse vaut N, on envoie un 1 sur la sortie, et on envoie un zéro sinon. Pour cela, j'ai donc besoin d'un comparateur pour chaque sortie, et le tour est joué. Précisons cependant que cette méthode gaspille beaucoup de circuits et qu'il y a une certaine redondance. En effet, les comparateurs ont souvent des portions de circuits qui sont identiques et ne diffèrent parfois que ce quelques portes logiques. En utilisant des comparateurs séparés, ces portions de circuits sont dupliquées, alors qu'il serait judicieux de partager. [[File:Internals of decoder.png|centre|vignette|upright=1.5|Exemple d'un décodeur à 8 sorties.]] Comme autre méthode, plus économe en circuits, on peut créer un décodeur en assemblant plusieurs décodeurs plus simples, nommés sous-décodeurs. Ces sous-décodeurs sont des décodeurs normaux, auxquels on a ajouté une entrée RAZ, qui permet de mettre à zéro toutes les sorties : si on met un 0 sur cette entrée, toutes les sorties passent à 0, alors que le décodeur fonctionne normalement sinon. Construire un décodeur demande suffisamment de sous-décodeurs pour combler toutes les sorties. Si on utilise des sous-décodeurs à n entrées, ceux-ci prendront en entrée les n bits de poids faible de l'entrée du décodeur que l'on souhaite construire (le décodeur final). Dans ces conditions, les n décodeurs auront une de leurs sorties à 1. Pour que le décodeur final se comporte comme il faut, il faut désactiver tous les sous-décodeurs, sauf un avec l'entrée RAZ. Pour commander les n bits RAZ des sous-décodeurs, il suffit d'utiliser un décodeur qui est commandé par les bits de poids fort du décodeur final. [[File:Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.jpg|centre|vignette|upright=1.5|Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.]] ==Le démultiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Un démultiplexeur a plusieurs sorties et une seule entrée. Les sorties sont numérotées de 0 à la valeur maximale. Il permet de sélectionner une sortie et de recopier l'entrée dessus, les autres sorties sont mises à 0. Pour séléctionner la sortie, le démultiplexeur possède une entrée de commande, sur laquelle on envoie le numéro de la sortie de destination. Comme le nom l'indique, le démultiplexeur fait l'exact inverse du multiplexeur, que nous verrons plus bas. ===Le démultiplexeur à deux sorties=== Le démultiplexeur le plus simple est le démultiplexeur à deux sorties. Il possède une entrée de donnée, une entrée de commande et deux sorties, toutes de 1 bit. Suivant la valeur du bit sur l'entrée de commande, il recopie le bit d'entrée, soit sur la première sortie, soit sur la seconde. Les deux sorties sont numérotées respectivement 0 et 1. [[File:Demultiplexer.png|centre|vignette|upright=1.5|Démultiplexeur à 2 sorties.]] On peut le concevoir facilement en partant de sa table de vérité. {|class="wikitable" |- ! Entrée de commande ''Select'' ! Entrée de donnée ''Input'' ! ! Sortie 1 ! Sortie 0 |- | 0 || 0 || || 0 || 0 |- |- | 0 || 1 || || 0 || 1 |- |- | 1 || 0 || || 0 || 0 |- |- | 1 || 1 || || 1 || 0 |- |} ===Les démultiplexeurs à plus de deux sorties=== Il est parfaitement possible de créer des démultiplexeurs en utilisant les méthodes du chapitre sur les circuits combinatoires, comme ma méthode des ''minterms'' ou les tableaux de Karnaugh. On obtient alors un démultiplexeur assez simple, composé de deux couches de portes logiques : une couche de portes NON et une couche de portes ET à plusieurs entrées. [[File:Demux.PNG|centre|vignette|upright=1.5|Démultiplexeur fabriqué avec une table de vérité.]] Mais cette méthode n'est pas pratique, car elle utilise beaucoup de portes logiques et que les portes logiques avec beaucoup d'entrées sont difficiles à fabriquer. Pour contourner ces problèmes, il est possible de créer des démultiplexeurs en assemblant des démultiplexeurs 1 vers 2. Évidemment, le même principe s'applique à des démultiplexeurs plus complexes : il suffit de rajouter des couches. [[File:Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.jpg|centre|vignette|upright=1.5|Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.]] Un démultiplexeur peut aussi se fabriquer en utilisant un décodeur et quelques portes ET. Pour comprendre pourquoi, regardons la table de vérité d'un démultiplexeur à quatre sorties. Si vous éliminez le cas où l'entrée de donnée ''Input'' vaut 0, et que vous tenez compte uniquement des entrées de commande, vous retombez sur la table de vérité d'un décodeur. Cela correspond aux cases en rouge. {|class="wikitable" |- ! Input !! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 0 || 1 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 1 || || 0 || 0 || 0 || 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |} En réalité, Le fonctionnement d'un démultiplexeur peut se résumer comme suit : soit l'entrée ''Input'' est à 1 et il fonctionne comme un décodeur dont l'entrée est l'entrée de commande, soit l'entrée ''Input'' vaut 0 et sa sortie est mise à 0. On devine donc qu'il faut combiner un décodeur avec le circuit de mise à zéro vu dans le chapitre précédent. On devine rapidement que l'entrée ''Input'' commande la mise à zéro de la sortie, ce qui donne le circuit suivant : [[File:Démultiplexeur conçu à partir d'un décodeur.jpg|centre|vignette|upright=1.5|Démultiplexeur conçu à partir d'un décodeur.]] ==Le multiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Les multiplexeurs sont des composants qui possèdent un nombre variable d'entrées, mais une seule sortie. Un multiplexeur permet de sélectionner une entrée et de recopier son contenu sur sa sortie, les entrées non-sélectionnées étant ignorées. Sélectionner l'entrée à recopier sur la sortie se fait en configurant une entrée de commande du multiplexeur. Les entrées sont numérotées de 0 à la valeur maximale. Configurer l'entrée de commande demande juste d'envoyer le numéro de l'entrée sélectionnée dessus. [[File:4-to-1 multiplexer.svg|centre|vignette|Multiplexeur à 4 entrées.]] Les multiplexeurs sont très utilisés et on en retrouve partout : dans les mémoires RAM, dans les processeurs, dans les circuits de calcul, dans les circuits pour communiquer avec les périphériques, et j'en passe. Il s'agit d'un composant très utilisé, qu'il est primordial de bien comprendre avant de passer à la suite du cours. ===Le multiplexeur à deux entrées=== Le multiplexeur le plus simple est le multiplexeur à deux entrées et une sortie. Il est facile de le construire avec des portes logiques, dans les implémentations les plus simples. Sachez toutefois que les multiplexeurs utilisés dans les ordinateurs récents ne sont pas forcément fabriqués avec des portes logiques, mais qu'on peut aussi les fabriquer directement avec des transistors. [[File:Multiplexeur à deux entrées - symbole.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - symbole.]] Pour commencer, établissons sa table de vérité. On va supposer qu'un 0 sur l'entrée de commande sélectionne l'entrée a. La table de vérité devrait être la suivante : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||0||0||0 |- |0||0||1||0 |- |0||1||0||1 |- |0||1||1||1 |- |1||0||0||0 |- |1||0||1||1 |- |1||1||0||0 |- |1||1||1||1 |} Sélectionnons les lignes qui mettent la sortie à 1 : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||1||0||1 |- |0||1||1||1 |- |1||0||1||1 |- |1||1||1||1 |} On sait maintenant quels comparateurs avec une constante utiliser. On peut, écrire l'équation logique du circuit. La première ligne donne l'équation suivante : <math>\overline{E_c} . a . \overline{b}</math>, la seconde donne l'équation <math>\overline{E_c} . a . b</math> , la troisième l'équation <math>E_c . \overline{a} . b</math> et la quatrième l'équation <math>E_c . a . b</math>. L'équation finale obtenue est donc : : <math>(\overline{E_c} . a . \overline{b}) + (\overline{E_c} . a . b) + (E_c . \overline{a} . b) + E_c . a . b</math> L'équation précédente est assez compliquée, mais il y a moyen de la simplifier assez radicalement. Pour cela, nous allons utiliser les règles de l’algèbre de Boole. Pour commencer, nous allons factoriser <math>(\overline{E_c}</math> et <math>E_c</math> : : <math> \left[ \overline{E_c} .[ (a . \overline{b}) + (a . b)] \right] + \left[ E_c . [(\overline{a} . b) + (a . b)] \right] </math> Ensuite, factorisons <math>a</math> dans le premier terme et <math>b</math> dans le second : : <math> \left[ \overline{E_c} . a . (\overline{b} + b) \right] + \left[ E_c . b . (\overline{a} + a) \right]</math> Les termes <math>\overline{b} + b</math> et <math>\overline{a} + a</math> valent 1 : : <math> \left[ \overline{E_c} . a . 1 \right] + \left[ E_c . b . 1 \right]</math> On sait que <math>a . 1 = a</math>, ce qui fait que l'équation simplifiée est la suivante : : <math>(\overline{E_c} . a) + (E_c . b)</math> Le circuit qui correspond est : [[File:Multiplexeur à deux entrées - circuit.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - circuit.]] ===Les multiplexeurs à plus de deux entrées=== Il est possible de concevoir un multiplexeur quelconque à partir de sa table de vérité. Le résultat est alors un circuit composé d'une porte OU à plusieurs entrées, de plusieurs portes ET, et de quelques portes NON. Un exemple est illustré ci-dessous. Vous remarquerez cependant que ce circuit a un défaut : la porte OU finale a beaucoup d'entrées, ce qui pose de nombreux problèmes techniques. Il est difficile de concevoir des portes logiques avec un très grand nombre d'entrées. Aussi, les applications à haute performance demandent d'utiliser d'autres solutions. [[File:Mux2.png|centre|vignette|upright=1.5|Multiplexeur conçu à partir de sa table de vérité.]] Une solution alternative conçoit un multiplexeur à plus de deux entrées en combinant des multiplexeurs plus simples. Par exemple, en prenant deux multiplexeurs plus simples, et en ajoutant un multiplexeur 2 vers 1 sur leurs sorties respectives. Le multiplexeur final se contente de sélectionner une sortie parmi les deux sorties des multiplexeurs précédents, qui ont déjà effectué une sorte de présélection. [[File:Multiplexeur conçu à partir de multiplexeurs plus simples.jpg|centre|vignette|upright=1.5|Multiplexeur conçu à partir de multiplexeurs plus simples.]] Il existe toutefois une manière bien plus simple pour créer un multiplexeur, qui utilise des opérations de masquage. L'idée est qu'un multiplexeur sélectionne un bit bien précis dans l'opérande. L'idée est de masquer les bits non-sélectionnés, puis de regarder le résultat après masquage. Par défaut, les bits non-sélectionnés sont mis à 0. Le résultat après masquage dépend de la valeur du bit sélectionné : * Si le bit sélectionné vaut 0, alors tous les bits après masquage sont à zéro. * Si le bit sélectionné vaut 1, alors seul un bit du résultat après masquage est à 1. Pour savoir si au moins un bit du résultat vaut 1, l'idée est d'utiliser une porte OU. Si tous les bits sont à 0, la porte OU donnera un zéro. Sinon, elle sortira un 1. La sortie du multiplexeur s'obtient donc en faisant un OU logique entre tous les bits du résultat après masquage. Le circuit au complet est donc composé d'un circuit de masquage, d'un circuit qui génère le masque, et d'une porte OU. Le circuit qui génère le masque transforme le numéro du bit en un masque adéquat. Si le numéro du bit est de N, le masque a son énième bit à 1, les autres à 0. Pour le dire autrement, il convertit le numéro du bit en sa représentation ''one-hot''. Et ce n'est ni plus ni moins que ce que fait un décodeur ! La génération du masque est donc le fait d'un décodeur. [[File:Multiplexeur 2 vers 4 conçu à partir d'un décodeur.png|centre|vignette|upright=2|Multiplexeur 2 vers 4 conçu à partir d'un décodeur]] ==L'encodeur== [[File:8 to 3 simple encoder IEC symbol.svg|vignette|upright=0.5|Encodeur à 8 entrées (et 3 sorties).]] Il existe un circuit qui fait exactement l'inverse du décodeur : c'est l''''encodeur'''. Là où les décodeurs ont une entrée de <math>N</math> bits et <math>2^N</math> sorties de 1 bit, l'encodeur a à l'inverse <math>2^N</math> entrées de 1 bit avec une sortie de <math>N</math> bits. Par exemple, un encodeur avec une entrée de 4 bits aura 2 sorties, un décodeur avec une entrée de 8 bits aura 3 sorties, un décodeur avec une entrée de 256 bits aura 8 sorties, etc. Comme pour les décodeurs, on parle d'un encodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 8 vers 3 pour un décodeur à 8 bits d'entrée et 3 de sortie, de décodeur 16 vers 4, etc. [[File:Encoder block diagram.jpg|centre|vignette|upright=1.5|Entrées et sorties d'un encodeur.]] De plus, contrairement au décodeur, ce sont les entrées qui sont numérotées de 0 à N et non les sorties. Dans ce qui suit, on va supposer qu'une seule des entrées est à 1. Il existe des encodeurs capables de traiter le cas où plusieurs bits d'entrée sont à 1, qui sont appelés des encodeurs à priorité, mais nous les laissons pour le chapitre suivant. Le chapitre suivant sera totalement dédié aux encodeurs à priorité, aussi nous préférons nous focaliser sur le cas d'un encodeur simple, capable de traiter uniquement le cas où une seule entrée est à 1. En sortie, l'encodeur donne le numéro de l'entrée qui est à 1. Par exemple, si l'entrée numéro 5 est à 1 et les autres à 0, alors l'encodeur envoie un 5 sur sa sortie. Une autre manière d'expliquer son fonctionnement est la suivant : un encodeur traduit un nombre codé en représentation ''one-hot'' vers du binaire normal. L'utilité d'un encodeur n'est pas très évidente à ce moment du cours, mais nous pouvons déjà dire qu'ils seront utiles dans certaines formes de mémoires RAM appelées des mémoires associatives, qui sont utilisées dans des routeurs, switchs et autre matériel réseau. La majorité des mémoires caches de nos ordinateurs sont de ce type, bien que leur implémentation exacte ne fasse pas usage d'un encodeur. Une autre utilisation est la transformation d'un nombre codé en représentation ''one-hot'' vers du binaire normal, chose marginalement utile. ===L'encodeur 4 vers 2=== Prenons l'exemple d'un encodeur à 4 entrées et 2 sorties. Écrivons sa table de vérité. D'après la description du circuit, on devrait trouver ceci : {|class="wikitable" |+ Table de vérité d'un encodeur 4 vers 2 |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 |- | 0 || 0 || 0 || 1 || || 0 || 0 |- | 0 || 0 || 1 || 0 || || 0 || 1 |- | 0 || 1 || 0 || 0 || || 1 || 0 |- | 1 || 0 || 0 || 0 || || 1 || 1 |} Vous voyez que la table de vérité est incomplète. En effet, l'encodeur fonctionne tant qu'une seule de ses entrées est à 1. L'encodeur dit alors quelle est la sortie à 1, mais cela suppose que les autres soient à 0. Si plusieurs entrées sont à 1, le comportement de l'encodeur est potentiellement erroné. En effet, il donnera un résultat incorrect sur certaines entrées. Mais passons cela sous silence et ne tenons compte que de la table de vérité partielle précédente. On peut traduire cette table de vérité en circuit logique. On obtient alors les équations suivantes : : <math>S1 = E3 + E2</math> : <math>S0 = E3 + E1</math> Le tout donne le circuit suivant : [[File:A Simple 4-2 encoder using or gate.jpg|centre|vignette|upright=1.5|Exemple d'encodeur à 4 entrées et 2 sorties.]] ===Les encodeurs à plus de deux sorties=== Il est possible de créer un encodeur complexe en combinant plusieurs encodeurs simples. C'est un peu la même chose qu'avec les décodeurs, pour lesquels on peut créer un décodeur 8 vers 256 à base de deux décodeurs 7 vers 128, ou de quatre décodeurs 6 vers 64. L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Pour comprendre l'idée, prenons la table de vérité d'un encodeur 8 vers 3; donnée dans le tableau ci-dessous. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 0 || 1 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 1 || 0 || || 0 || 0 || 1 |- | 0 || 0 || 0 || 0 || 0 || 1 || 0 || 0 || || 0 || 1 || 0 |- | 0 || 0 || 0 || 0 || 1 || 0 || 0 || 0 || || 0 || 1 || 1 |- | 0 || 0 || 0 || 1 || 0 || 0 || 0 || 0 || || 1 || 0 || 0 |- | 0 || 0 || 1 || 0 || 0 || 0 || 0 || 0 || || 1 || 0 || 1 |- | 0 || 1 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 0 |- | 1 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 1 |} En regardant bien, vous verrez que vous pouvez trouver la table de vérité d'un encodeur 4 vers 2 en deux exemplaires, indiquées en rouge. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |} On voit que les deux bits de poids faibles correspondent à la sortie de l'encodeur activé par l'entrée. Si le premier encodeur est activé, c'est lui qui fournit les bits de poids faibles. Inversement, si c'est le second encodeur qui a un résultat non-nul, c'est lui qui fournit les bits de poids faible. Notons que seul un des deux encodeurs a une sortie non-nulle à la fois : soit le premier a une sortie non-nulle, soit c'est le second, mais c'est impossible que ce soit les deux en même temps. Cela permet de déduire quelle opération permet de mixer les deux résultats : un simple OU logique suffit. Car, pour rappel, 0 OU X donne X, quelque que soit le X en question. Les bits de poids faible du résultat se calculent en faisant un OU entre les deux résultats des encodeurs. Ensuite, il faut déterminer comment fixer le bit de poids fort du résultat. Il vaut 0 si le premier encodeur a une entrée non-nulle, et 1 si c'est le premier encodeur qui a une entrée non-nulle. Pour cela, il suffit de vérifier si les bits de poids forts, associés au premier encodeur, contiennent un 1. Si c'est le cas, alors on met la troisième sortie à 1. [[File:Encodeur fabriqué à partir d'encodeurs plus petits.png|centre|vignette|upright=2|Encodeur fabriqué à partir d'encodeurs plus petits.]] Notons que cette procédure, à savoir faire un OU entre les sorties de deux encodeurs simples, puis faire un OU pour calculer le troisième bit, marche pour tout encodeur de taille quelconque. À vrai dire, le circuit obtenu plus haut d'un encodeur 4 vers 2 est conçu ainsi, mais en combinant deux encodeurs 2 vers 1. La procédure consiste à ajouter trois portes OU à deux encodeurs. Mais ceux-ci sont eux-même composés de portes OU associées à des encodeurs plus petits, et ainsi de suite. On peut poursuivre ainsi jusqu’à tomber sur des encodeurs 4 vers 2, qui sont eux-mêmes composés de deux portes OU. Au final, on se retrouve avec un circuit conçu uniquement à partir de portes OU. Notons qu'il est possible de simplifier le circuit obtenu avec la procédure en fusionnant des portes OU. Si on simplifie vraiment au maximum, le circuit consiste alors en une porte OU à plusieurs entrées par sortie, chacune étant connectée à certaines entrées bien précises. Pour un encodeur 8 vers 3, la simplification du circuit devrait donner ceci : [[File:8-3 Encoder.gif|centre|vignette|upright=1.5|Encodeur 8 vers 3.]] ==L'encodeur à priorité== L''''encodeur à priorité''' est un dérivé du circuit encodeur, vu dans la section précédente. La différence ne se situe pas dans le nombre d'entrée ou de sortie, ni même dans son interface extérieure. Comme pour l'encodeur normal, l'encodeur à priorité possède <math>2^N</math> entrées numérotées de 0 à <math>2^N - 1</math> et N sorties. Une autre manière plus intuitive de le dire est qu'il possède N entrées et <math>\log_2{N}</math> sorties. Pas de changement de ce point de vue. La différence entre encodeur simple et encodeur à priorité tient dans leur fonctionnement, dans le calcul qu'ils font. Avec un encodeur normal, on a supposé que seul un bit d'entrée pouvait être à 1, les autres étant systématiquement à 0. Si cette condition est naturellement remplie dans certains cas d’utilisation, ce n'est pas le cas dans d'autres. L'encodeur à priorité est un encodeur amélioré dans le sens où il donne un résultat valide même quand plusieurs bits d'entrée sont à 1. Il donne donc un résultat pour n'importe quel nombre passé en entrée. Mais avant de passer aux explications, un peu de terminologie utile. Dans ce qui suit, nous aurons à utiliser des expressions du type "le 1 de poids faible", "le 1 de poids fort" et quelques autres du même genre. Quand nous parlerons du 1 de poids faible, nous voudrons parler du premier 1 que l'on croise dans un nombre en partant de sa droite. Par exemple, dans le nombre 0110 1000, le 1 de poids faible est le quatrième bit. Quant au "1 de poids fort", c'est le premier 1 que l'on croise quand on parcourt le nombre à partir de sa gauche. Dans le cas le plus fréquent, l'encodeur à priorité prend en entrée un nombre et donne la position du 1 de poids fort. Mais dans d'autres cas, l'encodeur à priorité donne la position du 1 de poids faible. Il existe des équivalents, mais qui trouvent cette fois-ci les zéros de poids fort/faible, mais nous n'en parlerons pas dans ce chapitre. ===L'encodeur à priorité conçu à partir de sa table de vérité=== Il est possible de concevoir l'encodeur à priorité à partir de sa table de vérité, mais les méthodes des minterms ou des maxterms ne donnent pas de très bons résultats. Notons que ces encodeurs ont souvent une nouvelle entrée notée V, qui indique si la sortie est valide, et qui indique qu'au moins une entrée est à 1. Elle vaut 1 si au moins une entrée est à 1, 0 si toutes les entrées sont à 0. À titre d'exemple, la table de vérité d'un encodeur à priorité 4 vers 2 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. {|class="wikitable" |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 !! V |- | 0 || 0 || 0 || 0 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 1 || || 0 || 0 || 1 |- | 0 || 0 || 1 || X || || 0 || 1 || 1 |- | 0 || 1 || X || X || || 1 || 0 || 1 |- | 1 || X || X || X || || 1 || 1 || 1 |} Les équations logiques obtenues sont donc les suivantes : : <math>V = E3 + E2 + E1 + E0</math> : <math>S0 = E3 + (\overline{E3} . \overline{E2} . E1)</math> : <math>S1 = E3 + ( \overline{E3} . E2 )</math> On voit quelle est la logique de chaque équation. Pour chaque ligne de la table de vérité, il faut vérifier si les bits de poids fort sont à 0, suivi par un 1, les bits de poids faible après le 1 étant oubliées. Pour le bit de validité, il suffit de faire un OU entre toutes les entrées. Les deux dernières équations se simplifient en : : <math>S0 = E3 + (\overline{E2} . E1)</math> : <math>S1 = E3 + E2</math>, Le circuit obtenu est le suivant : [[File:Pr encoder 4x2.png|centre|vignette|upright=1.5|Encodeur à priorité 4 vers 2.]] La table de vérité d'un encodeur à priorité 8 vers 3 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. [[File:Encoder.JPG|centre|vignette|upright=2|Table de vérité d'un encodeur à priorité 8 vers 3.]] Utiliser la table de vérité a des défauts. Premièrement, ce n'est pas la meilleure des solutions pour des circuits avec un grand nombre d'entrée. Faire cela donne des tables de vérité rapidement importantes, mêmes pour des encodeurs avec peu de sorties. Le circuit final utilise beaucoup de portes logiques comparé aux autres méthodes. Les solutions alternatives que nous allons voir dans ce qui suit permettent de résoudre ces deux problèmes en même temps. ===Les encodeurs à priorité récursifs=== Une première solution consiste à créer un gros encodeur à base d'encodeurs plus petits.L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Naturellement, il est préférable d'utiliser plusieurs exemplaires d'un même encodeur, c'est à dire que pour une entrée de 256 bits, il vaut mieux utiliser soit deux décodeurs 7 vers 128, soit quatre décodeurs 6 vers 64, etc. La construction est similaire à celle vue dans le chapitre précédent, dans la section sur les encodeurs. La différence est que le OU entre les sorties des encodeurs est remplacé par un multiplexeur. Une version générale est illustrée ci-dessous. On voit que les encodeurs ont une sortie de résultat de X bits notée idx et une sortie de validité notée vld. La sortie de validité finale se calcule en combinant les sorties de validité de chaque encodeur. La sortie est par définition à 1 tant qu'un seul encodeur a une sortie non-nulle, donc quand un seul encodeur a un bit de validité à 1. En clair, c'est un simple OU entre les bits de validité. Reste à déterminer la sortie de donnée, celle qui donne la position du 1 de poids fort. On peut dire que si l'on utilise des encodeurs avec N bits de sortie, alors les N bits de poids faible du résultat seront donnés par le premier encodeur avec une sortie non-nulle. Les résultats de chaque encodeur donnent doncles X bits de poids faible, un seul résultat devant être sélectionné. Le résultat à sélectionner est le premier à avoir un résultat non-nul, donc à avoir un bit de validité à 1. En clair, on peut déterminer quel est le bon encodeur, le bon résultat, en analysant les bits de validité. Mieux : d'après ce qui a été dit, on peut deviner que l'analyse réalisée correspond à trouver la position du premier encodeur à avoir un bit de validité à 1. En clair, c'est l'opération réalisée par un encodeur à priorité lui-même. Tout cela permet de déterminer les N bits de poids faible, amis les autres bits, ceux de poids fort, sont encore à déterminer. Pour cela, on peut remarquer que ceux-ci sont eux-même fournit par l'encodeur à priorité qui commande le MUX. [[File:PE-recursion.svg|centre|vignette|upright=2|Construction d'un encodeur à priorité à partir d'encodeur à priorité plus petits.]] Notons qu'avec cette méthode, il est possible, mais pas très intuitif, de fabriquer un encodeur configurable, capable de se comporter soit comme un encodeur de type ''Find Highest Set'', soit de type ''Find First Set''. L'implémentation la plus simple demande de modifier le circuit qui combine les résultats pour qu'il soit configurable et puisse faire les deux opérations à la demande. ===L'encodeur à priorité avec un circuit d'isolation du 1 de poids fort/faible=== Une autre solution part d'un encodeur normal, auquel on ajoute un circuit qui se charge de sélectionner un seul des bits passé sur son entrée. Le circuit de gestion des priorités a pour fonction de trouver sélectionner un bit et de mettre les autres 1 à 0. Suivant le circuit de priorité considéré, le bit sélectionné est soit le 1 de poids fort, soit le 1 de poids faible. Dans certains cas, le circuit de priorité est configurable et peut trouver l'un ou l'autre suivant ce qu'on lui demande. Dans ce qui va suivre, nous allons partir du principe que l'on souhaite avoir un encodeur qui trouve le 1 de poids fort, sauf indication contraire. [[File:Encodeur à priorité.png|centre|vignette|upright=2|Encodeur à priorité.]] Une méthode assez pratique découpe le circuit de gestion des priorité en petites briques de bases, reliées les unes à la suite des autres. L'idée est que les briques de base sont connectées de manière à propager un signal de mise à zéro. Si une brique détecte un 1, elle envoie un signal aux briques précédentes/suivantes, qui leur dit de mettre leur sortie à zéro. Ce faisant, une fois le premier 1 trouvé, on est certain que les autres bits précédents/suivants sont mis à zéro. Suivant les connexions des briques de base, on peut obtenir soit un encodeur qui effectue l'opération ''Find First Set'', soit encodeur de type ''Find Highest Set'' et réciproquement. En fait, suivant que les briques soient reliées de droite à gauche ou de gauche à droite, on obtiendra l'un ou l'autre de ces deux encodeurs. [[File:Circuit de gestion des priorités.png|centre|vignette|upright=2|Circuit de gestion des priorités.]] Chaque brique de base peut soit recopier le bit en entrée, soit le mettre à zéro. Pour décider quoi faire, elle regarde le signal d'entrée RAZ (''Remise A Zéro''). Si le bit RAZ vaut 1, la sortie est mise à zéro automatiquement. Dans le cas contraire, le bit passé en entrée est recopié. De plus, chaque brique de base doit fournir un signal de remise à zéro RAZ à destination de la brique suivante. Ce signal RAZ de sortie est mis à 1 dans deux cas : soit si le bit d'entrée vaut, soit quand le signal d'entrée RAZ est à 1. Si vous cherchez à la concevoir à partir d'un table de vérité, vous obtiendrez ceci : {| |[[File:Brique de base du circuit de gestion des priorités d'un encodeur à priorité.png|vignette|Brique de base du circuit de gestion des priorités d'un encodeur à priorité.]] |[[File:Circuit de gestion des priorité - Circuit de la brique de base.png|vignette|upright=1.5|Circuit de gestion des priorité - Circuit de la brique de base.]] |} Le circuit complet d'un encodeur à priorité peut être déduit facilement à partir des raisonnements précédents. Après quelques simplifications, on peut obtenir le circuit suivant. On voit qu'on a ajouté une ligne de briques RAZ à l'encodeur 8 vers 3 vu plus haut. [[File:Koder priorytetowy.jpg|centre|vignette|upright=2|Encodeur à priorités]] Le défaut de cette méthode est que le circuit de gestion des priorité est assez lent. Dans le pire des cas, le signal de remise à zéro traverse toutes les briques de base, soit autant qu'il y a de bits d'entrée. Si chaque brique de base met un certain temps, le temps mis pour que le circuit de priorité fasse son travail est proportionnel au nombre de bits de l'entrée. Cela n'a l'air de rien, mais cela peut prendre un temps rédhibitoire pour les circuits de haute performance, destinés à fonctionner à haute fréquence. Pour ces circuits, on préfère que le temps de calcul soit proportionnel au logarithme du nombre de bits d'entrée, un temps proportionnel étant considéré comme trop lent, surtout pour des opérations simples comme celles étudiées ici. Une version légèrement différente de ce circuit est utilisée dans le processeur ARM1, un des tout premiers processeur ARM. L'encodeur à priorité était bidirectionnel, à savoir capable de déterminer soit la place du 1 de poids faible, soit du 1 de poids fort. Pour ceux qui veulent en savoir plus, et qui ont déjà un bagage solide en architecture des ordinateurs, voici un lien à ce sujet : : [https://www.righto.com/2016/01/more-arm1-processor-reverse-engineering.html More ARM1 processor reverse engineering: the priority encoder ] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les circuits de masquage | prevText=Les circuits de masquage | next=Les circuits incrémenteurs/décrémenteurs | nextText=Les circuits incrémenteurs/décrémenteurs }} </noinclude> avd2vy5u1v0vqcnowjwc70409xx8e6k 768492 768491 2026-06-24T16:04:30Z Mewtow 31375 /* Les multiplexeurs à plus de deux entrées */ 768492 wikitext text/x-wiki Dans les chapitres précédents, nous avons vu comment fabriquer des circuits relativement généraux. Il est maintenant temps de voir quelques circuits relativement simples, très utilisés. Ces circuits simples sont utilisés pour construire des circuits plus complexes, comme des processeurs, des mémoires, et bien d'autres. Les prochains chapitres vont se concentrer exclusivement sur ces circuits simples, mais courants. Nous allons donner quelques exemples de circuits assez fréquents dans un ordinateur et voir comment construire ceux-ci avec des portes logiques. Dans ce chapitre, nous allons nous concentrer sur quelques circuits, que j'ai décidé de regrouper sous le nom de '''circuits de sélection'''. Les circuits que nous allons présenter sont utilisés dans les mémoires, ainsi que dans certains circuits de calcul. Il est important de bien mémoriser ces circuits, ainsi que la procédure pour les concevoir : nous en aurons besoin dans la suite du cours. Ils sont au nombre de quatre : le décodeur, l'encodeur, le multiplexeur et le démultiplexeur. ==Le décodeur== [[File:DECODER 3 vers 8.png|vignette|Décodeur à 3 entrées et 8 sorties.]] Le premier circuit que nous allons voir est le '''décodeur''', un composant qui contient un grand nombre d'entrées et de sorties, avec des sorties qui sont numérotées. Un décodeur possède une entrée sur laquelle on envoie un nombre codé <math>N</math> bits et <math>2^N</math> sorties de 1 bit. Par exemple, un décodeur avec une entrée de 2 bits aura 4 sorties, un décodeur avec une entrée de 3 bits aura 8 sorties, un décodeur avec une entrée de 8 bits aura 256 sorties, etc. Généralement, on précise le nombre de bits d'entrée et de sortie comme suit : on parle d'un décodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 3 vers 8 pour un décodeur à 3 bits d'entrée et 8 de sortie, de décodeur 4 vers 16, etc. Le fonctionnement d'un décodeur est très simple : il prend sur son entrée un nombre entier x codé en binaire, puis il positionne à 1 la sortie numérotée x et met à zéro toutes les autres sorties. Par exemple, si on envoie la valeur 6 sur ses entrées, il mettra la sortie numéro 6 à 1 et les autres à zéro. Pour résumer, un décodeur est un circuit : * avec une entrée de <math>N</math> bits ; * avec <math>2^N</math> sorties de 1 bit ; * où les sorties sont numérotées en partant de zéro ; * où on ne peut sélectionner qu'une seule sortie à la fois : une seule sortie devra être placée à 1, et toutes les autres à zéro ; * et où deux nombres d'entrée différents devront sélectionner des sorties différentes : la sortie de notre contrôleur qui sera mise à 1 sera différente pour deux nombres différents placés sur son entrée. Une autre manière d'expliquer leur fonctionnement est qu'il traduisent un nombre encodé en binaire vers la représentation ''one-hot''. Pour rappel, sur cette dernière, le nombre N est encodé en mettant le énième bit à 1, les autres sont à 0. Le bit de poids faible compte pour le zéro. Les décodeurs sont très utilisés, au point que faire la liste de leurs utilisations serait bien trop long. Par contre, on peut d'or et déjà prévenir que les décodeurs sont utilisés dans toutes les mémoires RAM et ROM, présentes dans tout ordinateur. La RAM de votre ordinateur contient un ou plusieurs décodeurs, idem pour la mémoire caché intégrée dans le processeur, etc. C'est donc un circuit absolument primordial à étudier, qui reviendra souvent dans ce cours. ===La table de vérité d'un décodeur=== Au vu de ce qui vient d'être dit, on peut facilement écrire la table de vérité d'un décodeur. Pour l'exemple, prenons un décodeur 2 vers 4, pour simplifier la table de vérité. Voici sa table de vérité complète, c’est-à-dire qui contient toutes les sorties regroupées : {|class="wikitable" |- ! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || || 1 || 0 || 0 || 0 |- | 0 || 1 || || 0 || 1 || 0 || 0 |- | 1 || 0 || || 0 || 0 || 1 || 0 |- | 1 || 1 || || 0 || 0 || 0 || 1 |} Vous remarquerez que la table de vérité est assez spéciale. Les seuls bits à 1 sont sur la diagonale. Et cela ne vaut pas que dans l'exemple choisit, mais cela se généralise pour tous les décodeurs. Sur chaque ligne, il n'y a qu'un seul bit à 1, ce qui traduit le fait qu'une entrée ne met qu'une seule sortie est à 1 et met les autres à 0. Si on traduit la table de vérité sous la forme d'équations logiques et de circuit, on obtient ceci : [[File:Decoder Example.svg|centre|vignette|upright=2|Equations logiques et circuit d'un décodeur 2 vers 4.]] Le circuit obtenu est le suivant : [[File:2to4demux.svg|centre|vignette|upright=2|Démultiplexeur à deux sorties.]] Il y a des choses intéressantes à remarquer sur les équations logiques. Pour rappel, l'équation logique d'une sortie est composée, dans le cas général, soit d'un minterm unique, soit d'un OU entre plusieurs minterms. Chaque minterm est l'équation d'un circuit qui compare l'entrée à un nombre bien précis et dépendant du minterm. Si on regarde bien, l'équation de chaque sortie correspond à un minterm et à rien d'autre, il n'y a pas de OU entre plusieurs minterms. Les minterms sont de plus différents pour chaque sortie et on ne trouve pas deux sorties avec le même minterm. Enfin, chaque minterm possible est présent : X bits d'entrée nous donnent 2^X entrées différentes possibles, donc 2^X minterms possibles. Et il se trouve que tous ces minterms possibles sont représentés dans un décodeur, ils ont tous leur sortie associée. C'est une autre manière de définir un décodeur : toutes ses sorties codent un minterm, deux sorties différentes ont des minterms différents et tous les minterms possibles sur n bits sont représentés. Ces informations vont nous être utiles pour la suite. En effet, grâce à elles, nous allons en déduire une méthode générale pour fabriquer un décodeur, peu importe son nombre de bits d'entrée et de sortie. Mais elles permettent aussi de montrer que l'on peut créer n'importe quel circuit combinatoire quelconque à partir d'un décodeur et de quelques portes logiques. Dans ce qui suit, on suppose que le circuit combinatoire en question a une entrée de n bits et une seule sortie de 1 bit. Pour rappel, ce genre de circuit se conçoit en utilisant une table de vérité qu'on traduit en équations logiques, puis en circuits. Le circuit obtenu est alors soit un simple minterm, soit un OU entre plusieurs minterms. Or, le décodeur contient tous les minterms possibles pour une entrée de n bits, avec un minterm par sortie. Il suffit donc de prendre une porte OU et de la connecter aux minterms/sorties adéquats. [[File:Conception d'un circuit combinatoire quelconque à partir d'un décodeur.jpg|centre|vignette|upright=2|Conception d'un circuit combinatoire quelconque à partir d'un décodeur.]] Fabriquer un circuit combinatoire avec un décodeur gaspille pas mal de portes logiques. En effet, le décodeur fournit tous les minterms possibles, alors que seule une minorité est réellement utilisée pour fabriquer le circuit combinatoire. Les minterms en trop correspondent à des paquets de portes NON et ET reliées entre elles, qui ne servent à rien. De plus, les minterms ne sont pas simplifiés. On ne peut pas utiliser les techniques vues dans les chapitres précédents pour simplifier les minterms et réduire le nombre de portes logiques utilisées. Le décodeur reste tel qu'il est, avec l'ensemble des minterms non-simplifiés. Mais la simplicité de conception du circuit reste un avantage dans certaines situations. Notamment, les circuits avec plusieurs bits de sortie sont faciles à fabriquer, notamment si les sorties partagent des minterms (si un minterm est présent dans l'équation de plusieurs sorties différentes, l'usage d'un décodeur permet de facilement factoriser celui-ci). Ceci étant dit, passons à la conception d'un décodeur avec des portes logiques. ===L'intérieur d'un décodeur=== On vient de voir que chaque sortie d'un décodeur correspond à son propre minterm, et que tous les minterms possibles sont représentés. Rappelons que chaque minterm est associé à un circuit qui compare l'entrée à une constante X, X dépendant du minterm. En combinant ces deux informations, on devine qu'un décodeur est simplement composé de comparateurs avec une constante que de minterms/sorties. Par exemple, si je prends un décodeur 7 vers 128, cela veut dire qu'on peut envoyer en entrée un nombre codé entre 0 et 127 et que chaque nombre aura son propre minterm associé : il y aura un minterm qui vérifie si l'entrée vaut 0, un autre vérifie si elle vaut 1, un autre qui vérifie si elle vaut 2, ... , un minterm qui vérifie si l'entrée vaut 126, et enfin un minterm qui vérifie si l'entrée vaut 127. Pour reformuler d'une manière bien plus simple, on peut voir les choses comme suit. Si l'entrée du décodeur vaut N, la sortie mise à 1 est la sortie N. Bref, déduire quand mettre à 1 la sortie N est facile : il suffit de comparer l'entrée avec N. Si l'adresse vaut N, on envoie un 1 sur la sortie, et on envoie un zéro sinon. Pour cela, j'ai donc besoin d'un comparateur pour chaque sortie, et le tour est joué. Précisons cependant que cette méthode gaspille beaucoup de circuits et qu'il y a une certaine redondance. En effet, les comparateurs ont souvent des portions de circuits qui sont identiques et ne diffèrent parfois que ce quelques portes logiques. En utilisant des comparateurs séparés, ces portions de circuits sont dupliquées, alors qu'il serait judicieux de partager. [[File:Internals of decoder.png|centre|vignette|upright=1.5|Exemple d'un décodeur à 8 sorties.]] Comme autre méthode, plus économe en circuits, on peut créer un décodeur en assemblant plusieurs décodeurs plus simples, nommés sous-décodeurs. Ces sous-décodeurs sont des décodeurs normaux, auxquels on a ajouté une entrée RAZ, qui permet de mettre à zéro toutes les sorties : si on met un 0 sur cette entrée, toutes les sorties passent à 0, alors que le décodeur fonctionne normalement sinon. Construire un décodeur demande suffisamment de sous-décodeurs pour combler toutes les sorties. Si on utilise des sous-décodeurs à n entrées, ceux-ci prendront en entrée les n bits de poids faible de l'entrée du décodeur que l'on souhaite construire (le décodeur final). Dans ces conditions, les n décodeurs auront une de leurs sorties à 1. Pour que le décodeur final se comporte comme il faut, il faut désactiver tous les sous-décodeurs, sauf un avec l'entrée RAZ. Pour commander les n bits RAZ des sous-décodeurs, il suffit d'utiliser un décodeur qui est commandé par les bits de poids fort du décodeur final. [[File:Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.jpg|centre|vignette|upright=1.5|Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.]] ==Le démultiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Un démultiplexeur a plusieurs sorties et une seule entrée. Les sorties sont numérotées de 0 à la valeur maximale. Il permet de sélectionner une sortie et de recopier l'entrée dessus, les autres sorties sont mises à 0. Pour séléctionner la sortie, le démultiplexeur possède une entrée de commande, sur laquelle on envoie le numéro de la sortie de destination. Comme le nom l'indique, le démultiplexeur fait l'exact inverse du multiplexeur, que nous verrons plus bas. ===Le démultiplexeur à deux sorties=== Le démultiplexeur le plus simple est le démultiplexeur à deux sorties. Il possède une entrée de donnée, une entrée de commande et deux sorties, toutes de 1 bit. Suivant la valeur du bit sur l'entrée de commande, il recopie le bit d'entrée, soit sur la première sortie, soit sur la seconde. Les deux sorties sont numérotées respectivement 0 et 1. [[File:Demultiplexer.png|centre|vignette|upright=1.5|Démultiplexeur à 2 sorties.]] On peut le concevoir facilement en partant de sa table de vérité. {|class="wikitable" |- ! Entrée de commande ''Select'' ! Entrée de donnée ''Input'' ! ! Sortie 1 ! Sortie 0 |- | 0 || 0 || || 0 || 0 |- |- | 0 || 1 || || 0 || 1 |- |- | 1 || 0 || || 0 || 0 |- |- | 1 || 1 || || 1 || 0 |- |} ===Les démultiplexeurs à plus de deux sorties=== Il est parfaitement possible de créer des démultiplexeurs en utilisant les méthodes du chapitre sur les circuits combinatoires, comme ma méthode des ''minterms'' ou les tableaux de Karnaugh. On obtient alors un démultiplexeur assez simple, composé de deux couches de portes logiques : une couche de portes NON et une couche de portes ET à plusieurs entrées. [[File:Demux.PNG|centre|vignette|upright=1.5|Démultiplexeur fabriqué avec une table de vérité.]] Mais cette méthode n'est pas pratique, car elle utilise beaucoup de portes logiques et que les portes logiques avec beaucoup d'entrées sont difficiles à fabriquer. Pour contourner ces problèmes, il est possible de créer des démultiplexeurs en assemblant des démultiplexeurs 1 vers 2. Évidemment, le même principe s'applique à des démultiplexeurs plus complexes : il suffit de rajouter des couches. [[File:Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.jpg|centre|vignette|upright=1.5|Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.]] Un démultiplexeur peut aussi se fabriquer en utilisant un décodeur et quelques portes ET. Pour comprendre pourquoi, regardons la table de vérité d'un démultiplexeur à quatre sorties. Si vous éliminez le cas où l'entrée de donnée ''Input'' vaut 0, et que vous tenez compte uniquement des entrées de commande, vous retombez sur la table de vérité d'un décodeur. Cela correspond aux cases en rouge. {|class="wikitable" |- ! Input !! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 0 || 1 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 1 || || 0 || 0 || 0 || 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |} En réalité, Le fonctionnement d'un démultiplexeur peut se résumer comme suit : soit l'entrée ''Input'' est à 1 et il fonctionne comme un décodeur dont l'entrée est l'entrée de commande, soit l'entrée ''Input'' vaut 0 et sa sortie est mise à 0. On devine donc qu'il faut combiner un décodeur avec le circuit de mise à zéro vu dans le chapitre précédent. On devine rapidement que l'entrée ''Input'' commande la mise à zéro de la sortie, ce qui donne le circuit suivant : [[File:Démultiplexeur conçu à partir d'un décodeur.jpg|centre|vignette|upright=1.5|Démultiplexeur conçu à partir d'un décodeur.]] ==Le multiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Les multiplexeurs sont des composants qui possèdent un nombre variable d'entrées, mais une seule sortie. Un multiplexeur permet de sélectionner une entrée et de recopier son contenu sur sa sortie, les entrées non-sélectionnées étant ignorées. Sélectionner l'entrée à recopier sur la sortie se fait en configurant une entrée de commande du multiplexeur. Les entrées sont numérotées de 0 à la valeur maximale. Configurer l'entrée de commande demande juste d'envoyer le numéro de l'entrée sélectionnée dessus. [[File:4-to-1 multiplexer.svg|centre|vignette|Multiplexeur à 4 entrées.]] Les multiplexeurs sont très utilisés et on en retrouve partout : dans les mémoires RAM, dans les processeurs, dans les circuits de calcul, dans les circuits pour communiquer avec les périphériques, et j'en passe. Il s'agit d'un composant très utilisé, qu'il est primordial de bien comprendre avant de passer à la suite du cours. ===Le multiplexeur à deux entrées=== Le multiplexeur le plus simple est le multiplexeur à deux entrées et une sortie. Il est facile de le construire avec des portes logiques, dans les implémentations les plus simples. Sachez toutefois que les multiplexeurs utilisés dans les ordinateurs récents ne sont pas forcément fabriqués avec des portes logiques, mais qu'on peut aussi les fabriquer directement avec des transistors. [[File:Multiplexeur à deux entrées - symbole.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - symbole.]] Pour commencer, établissons sa table de vérité. On va supposer qu'un 0 sur l'entrée de commande sélectionne l'entrée a. La table de vérité devrait être la suivante : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||0||0||0 |- |0||0||1||0 |- |0||1||0||1 |- |0||1||1||1 |- |1||0||0||0 |- |1||0||1||1 |- |1||1||0||0 |- |1||1||1||1 |} Sélectionnons les lignes qui mettent la sortie à 1 : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||1||0||1 |- |0||1||1||1 |- |1||0||1||1 |- |1||1||1||1 |} On sait maintenant quels comparateurs avec une constante utiliser. On peut, écrire l'équation logique du circuit. La première ligne donne l'équation suivante : <math>\overline{E_c} . a . \overline{b}</math>, la seconde donne l'équation <math>\overline{E_c} . a . b</math> , la troisième l'équation <math>E_c . \overline{a} . b</math> et la quatrième l'équation <math>E_c . a . b</math>. L'équation finale obtenue est donc : : <math>(\overline{E_c} . a . \overline{b}) + (\overline{E_c} . a . b) + (E_c . \overline{a} . b) + E_c . a . b</math> L'équation précédente est assez compliquée, mais il y a moyen de la simplifier assez radicalement. Pour cela, nous allons utiliser les règles de l’algèbre de Boole. Pour commencer, nous allons factoriser <math>(\overline{E_c}</math> et <math>E_c</math> : : <math> \left[ \overline{E_c} .[ (a . \overline{b}) + (a . b)] \right] + \left[ E_c . [(\overline{a} . b) + (a . b)] \right] </math> Ensuite, factorisons <math>a</math> dans le premier terme et <math>b</math> dans le second : : <math> \left[ \overline{E_c} . a . (\overline{b} + b) \right] + \left[ E_c . b . (\overline{a} + a) \right]</math> Les termes <math>\overline{b} + b</math> et <math>\overline{a} + a</math> valent 1 : : <math> \left[ \overline{E_c} . a . 1 \right] + \left[ E_c . b . 1 \right]</math> On sait que <math>a . 1 = a</math>, ce qui fait que l'équation simplifiée est la suivante : : <math>(\overline{E_c} . a) + (E_c . b)</math> Le circuit qui correspond est : [[File:Multiplexeur à deux entrées - circuit.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - circuit.]] ===Les multiplexeurs à plus de deux entrées=== Il est possible de concevoir un multiplexeur quelconque à partir de sa table de vérité. Le résultat est alors un circuit composé d'une porte OU à plusieurs entrées, de plusieurs portes ET, et de quelques portes NON. Un exemple est illustré ci-dessous. Vous remarquerez cependant que ce circuit a un défaut : la porte OU finale a beaucoup d'entrées, ce qui pose de nombreux problèmes techniques. Il est difficile de concevoir des portes logiques avec un très grand nombre d'entrées. Aussi, les applications à haute performance demandent d'utiliser d'autres solutions. [[File:Mux2.png|centre|vignette|upright=1.5|Multiplexeur conçu à partir de sa table de vérité.]] Une solution alternative conçoit un multiplexeur à plus de deux entrées en combinant des multiplexeurs plus simples. Par exemple, en prenant deux multiplexeurs plus simples, et en ajoutant un multiplexeur 2 vers 1 sur leurs sorties respectives. Le multiplexeur final se contente de sélectionner une sortie parmi les deux sorties des multiplexeurs précédents, qui ont déjà effectué une sorte de présélection. [[File:Multiplexeur conçu à partir de multiplexeurs plus simples.jpg|centre|vignette|upright=1.5|Multiplexeur conçu à partir de multiplexeurs plus simples.]] Il existe toutefois une manière bien plus simple pour créer un multiplexeur, qui utilise des opérations de masquage. L'idée est qu'un multiplexeur sélectionne un bit bien précis dans l'opérande. L'idée est de masquer les bits non-sélectionnés, puis de regarder le résultat après masquage. Par défaut, les bits non-sélectionnés sont mis à 0. Le résultat après masquage dépend de la valeur du bit sélectionné : * Si le bit sélectionné vaut 0, alors tous les bits après masquage sont à zéro. * Si le bit sélectionné vaut 1, alors seul un bit du résultat après masquage est à 1. Pour savoir si au moins un bit du résultat vaut 1, l'idée est d'utiliser une porte OU. Si tous les bits sont à 0, la porte OU donnera un zéro. Sinon, elle sortira un 1. La sortie du multiplexeur s'obtient donc en faisant un OU logique entre tous les bits du résultat après masquage. Le circuit au complet est donc composé d'un circuit de masquage, d'un circuit qui génère le masque, et d'une porte OU. Le circuit qui génère le masque transforme le numéro du bit en un masque adéquat. Si le numéro du bit est de N, le masque a son énième bit à 1, les autres à 0. Pour le dire autrement, il convertit le numéro du bit en sa représentation ''one-hot''. Et ce n'est ni plus ni moins que ce que fait un décodeur ! La génération du masque est donc le fait d'un décodeur. [[File:Multiplexeur 2 vers 4 conçu à partir d'un décodeur.png|centre|vignette|upright=2|Multiplexeur 2 vers 4 conçu à partir d'un décodeur]] Le circuit précédent n'est cependant pas parfaitement optimisé. En effet, le décodeur est composé d'une couche de portes NON et d'une couche de portes ET. Il est possible de fusionner les portes ET du décodeur, avec celles liée au masque. Le résultat est une simplification qui élimine quelques portes logiques. Voici ce que cela donne pour un multiplexeur 4 vers 1 : [[File:4to1mux.png|centre|vignette|upright=2|Multiplexeur 2 vers 4, conçu à partir d'un décodeur, simplifié pour éviter des redondances.]] ==L'encodeur== [[File:8 to 3 simple encoder IEC symbol.svg|vignette|upright=0.5|Encodeur à 8 entrées (et 3 sorties).]] Il existe un circuit qui fait exactement l'inverse du décodeur : c'est l''''encodeur'''. Là où les décodeurs ont une entrée de <math>N</math> bits et <math>2^N</math> sorties de 1 bit, l'encodeur a à l'inverse <math>2^N</math> entrées de 1 bit avec une sortie de <math>N</math> bits. Par exemple, un encodeur avec une entrée de 4 bits aura 2 sorties, un décodeur avec une entrée de 8 bits aura 3 sorties, un décodeur avec une entrée de 256 bits aura 8 sorties, etc. Comme pour les décodeurs, on parle d'un encodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 8 vers 3 pour un décodeur à 8 bits d'entrée et 3 de sortie, de décodeur 16 vers 4, etc. [[File:Encoder block diagram.jpg|centre|vignette|upright=1.5|Entrées et sorties d'un encodeur.]] De plus, contrairement au décodeur, ce sont les entrées qui sont numérotées de 0 à N et non les sorties. Dans ce qui suit, on va supposer qu'une seule des entrées est à 1. Il existe des encodeurs capables de traiter le cas où plusieurs bits d'entrée sont à 1, qui sont appelés des encodeurs à priorité, mais nous les laissons pour le chapitre suivant. Le chapitre suivant sera totalement dédié aux encodeurs à priorité, aussi nous préférons nous focaliser sur le cas d'un encodeur simple, capable de traiter uniquement le cas où une seule entrée est à 1. En sortie, l'encodeur donne le numéro de l'entrée qui est à 1. Par exemple, si l'entrée numéro 5 est à 1 et les autres à 0, alors l'encodeur envoie un 5 sur sa sortie. Une autre manière d'expliquer son fonctionnement est la suivant : un encodeur traduit un nombre codé en représentation ''one-hot'' vers du binaire normal. L'utilité d'un encodeur n'est pas très évidente à ce moment du cours, mais nous pouvons déjà dire qu'ils seront utiles dans certaines formes de mémoires RAM appelées des mémoires associatives, qui sont utilisées dans des routeurs, switchs et autre matériel réseau. La majorité des mémoires caches de nos ordinateurs sont de ce type, bien que leur implémentation exacte ne fasse pas usage d'un encodeur. Une autre utilisation est la transformation d'un nombre codé en représentation ''one-hot'' vers du binaire normal, chose marginalement utile. ===L'encodeur 4 vers 2=== Prenons l'exemple d'un encodeur à 4 entrées et 2 sorties. Écrivons sa table de vérité. D'après la description du circuit, on devrait trouver ceci : {|class="wikitable" |+ Table de vérité d'un encodeur 4 vers 2 |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 |- | 0 || 0 || 0 || 1 || || 0 || 0 |- | 0 || 0 || 1 || 0 || || 0 || 1 |- | 0 || 1 || 0 || 0 || || 1 || 0 |- | 1 || 0 || 0 || 0 || || 1 || 1 |} Vous voyez que la table de vérité est incomplète. En effet, l'encodeur fonctionne tant qu'une seule de ses entrées est à 1. L'encodeur dit alors quelle est la sortie à 1, mais cela suppose que les autres soient à 0. Si plusieurs entrées sont à 1, le comportement de l'encodeur est potentiellement erroné. En effet, il donnera un résultat incorrect sur certaines entrées. Mais passons cela sous silence et ne tenons compte que de la table de vérité partielle précédente. On peut traduire cette table de vérité en circuit logique. On obtient alors les équations suivantes : : <math>S1 = E3 + E2</math> : <math>S0 = E3 + E1</math> Le tout donne le circuit suivant : [[File:A Simple 4-2 encoder using or gate.jpg|centre|vignette|upright=1.5|Exemple d'encodeur à 4 entrées et 2 sorties.]] ===Les encodeurs à plus de deux sorties=== Il est possible de créer un encodeur complexe en combinant plusieurs encodeurs simples. C'est un peu la même chose qu'avec les décodeurs, pour lesquels on peut créer un décodeur 8 vers 256 à base de deux décodeurs 7 vers 128, ou de quatre décodeurs 6 vers 64. L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Pour comprendre l'idée, prenons la table de vérité d'un encodeur 8 vers 3; donnée dans le tableau ci-dessous. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 0 || 1 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 1 || 0 || || 0 || 0 || 1 |- | 0 || 0 || 0 || 0 || 0 || 1 || 0 || 0 || || 0 || 1 || 0 |- | 0 || 0 || 0 || 0 || 1 || 0 || 0 || 0 || || 0 || 1 || 1 |- | 0 || 0 || 0 || 1 || 0 || 0 || 0 || 0 || || 1 || 0 || 0 |- | 0 || 0 || 1 || 0 || 0 || 0 || 0 || 0 || || 1 || 0 || 1 |- | 0 || 1 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 0 |- | 1 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 1 |} En regardant bien, vous verrez que vous pouvez trouver la table de vérité d'un encodeur 4 vers 2 en deux exemplaires, indiquées en rouge. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |} On voit que les deux bits de poids faibles correspondent à la sortie de l'encodeur activé par l'entrée. Si le premier encodeur est activé, c'est lui qui fournit les bits de poids faibles. Inversement, si c'est le second encodeur qui a un résultat non-nul, c'est lui qui fournit les bits de poids faible. Notons que seul un des deux encodeurs a une sortie non-nulle à la fois : soit le premier a une sortie non-nulle, soit c'est le second, mais c'est impossible que ce soit les deux en même temps. Cela permet de déduire quelle opération permet de mixer les deux résultats : un simple OU logique suffit. Car, pour rappel, 0 OU X donne X, quelque que soit le X en question. Les bits de poids faible du résultat se calculent en faisant un OU entre les deux résultats des encodeurs. Ensuite, il faut déterminer comment fixer le bit de poids fort du résultat. Il vaut 0 si le premier encodeur a une entrée non-nulle, et 1 si c'est le premier encodeur qui a une entrée non-nulle. Pour cela, il suffit de vérifier si les bits de poids forts, associés au premier encodeur, contiennent un 1. Si c'est le cas, alors on met la troisième sortie à 1. [[File:Encodeur fabriqué à partir d'encodeurs plus petits.png|centre|vignette|upright=2|Encodeur fabriqué à partir d'encodeurs plus petits.]] Notons que cette procédure, à savoir faire un OU entre les sorties de deux encodeurs simples, puis faire un OU pour calculer le troisième bit, marche pour tout encodeur de taille quelconque. À vrai dire, le circuit obtenu plus haut d'un encodeur 4 vers 2 est conçu ainsi, mais en combinant deux encodeurs 2 vers 1. La procédure consiste à ajouter trois portes OU à deux encodeurs. Mais ceux-ci sont eux-même composés de portes OU associées à des encodeurs plus petits, et ainsi de suite. On peut poursuivre ainsi jusqu’à tomber sur des encodeurs 4 vers 2, qui sont eux-mêmes composés de deux portes OU. Au final, on se retrouve avec un circuit conçu uniquement à partir de portes OU. Notons qu'il est possible de simplifier le circuit obtenu avec la procédure en fusionnant des portes OU. Si on simplifie vraiment au maximum, le circuit consiste alors en une porte OU à plusieurs entrées par sortie, chacune étant connectée à certaines entrées bien précises. Pour un encodeur 8 vers 3, la simplification du circuit devrait donner ceci : [[File:8-3 Encoder.gif|centre|vignette|upright=1.5|Encodeur 8 vers 3.]] ==L'encodeur à priorité== L''''encodeur à priorité''' est un dérivé du circuit encodeur, vu dans la section précédente. La différence ne se situe pas dans le nombre d'entrée ou de sortie, ni même dans son interface extérieure. Comme pour l'encodeur normal, l'encodeur à priorité possède <math>2^N</math> entrées numérotées de 0 à <math>2^N - 1</math> et N sorties. Une autre manière plus intuitive de le dire est qu'il possède N entrées et <math>\log_2{N}</math> sorties. Pas de changement de ce point de vue. La différence entre encodeur simple et encodeur à priorité tient dans leur fonctionnement, dans le calcul qu'ils font. Avec un encodeur normal, on a supposé que seul un bit d'entrée pouvait être à 1, les autres étant systématiquement à 0. Si cette condition est naturellement remplie dans certains cas d’utilisation, ce n'est pas le cas dans d'autres. L'encodeur à priorité est un encodeur amélioré dans le sens où il donne un résultat valide même quand plusieurs bits d'entrée sont à 1. Il donne donc un résultat pour n'importe quel nombre passé en entrée. Mais avant de passer aux explications, un peu de terminologie utile. Dans ce qui suit, nous aurons à utiliser des expressions du type "le 1 de poids faible", "le 1 de poids fort" et quelques autres du même genre. Quand nous parlerons du 1 de poids faible, nous voudrons parler du premier 1 que l'on croise dans un nombre en partant de sa droite. Par exemple, dans le nombre 0110 1000, le 1 de poids faible est le quatrième bit. Quant au "1 de poids fort", c'est le premier 1 que l'on croise quand on parcourt le nombre à partir de sa gauche. Dans le cas le plus fréquent, l'encodeur à priorité prend en entrée un nombre et donne la position du 1 de poids fort. Mais dans d'autres cas, l'encodeur à priorité donne la position du 1 de poids faible. Il existe des équivalents, mais qui trouvent cette fois-ci les zéros de poids fort/faible, mais nous n'en parlerons pas dans ce chapitre. ===L'encodeur à priorité conçu à partir de sa table de vérité=== Il est possible de concevoir l'encodeur à priorité à partir de sa table de vérité, mais les méthodes des minterms ou des maxterms ne donnent pas de très bons résultats. Notons que ces encodeurs ont souvent une nouvelle entrée notée V, qui indique si la sortie est valide, et qui indique qu'au moins une entrée est à 1. Elle vaut 1 si au moins une entrée est à 1, 0 si toutes les entrées sont à 0. À titre d'exemple, la table de vérité d'un encodeur à priorité 4 vers 2 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. {|class="wikitable" |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 !! V |- | 0 || 0 || 0 || 0 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 1 || || 0 || 0 || 1 |- | 0 || 0 || 1 || X || || 0 || 1 || 1 |- | 0 || 1 || X || X || || 1 || 0 || 1 |- | 1 || X || X || X || || 1 || 1 || 1 |} Les équations logiques obtenues sont donc les suivantes : : <math>V = E3 + E2 + E1 + E0</math> : <math>S0 = E3 + (\overline{E3} . \overline{E2} . E1)</math> : <math>S1 = E3 + ( \overline{E3} . E2 )</math> On voit quelle est la logique de chaque équation. Pour chaque ligne de la table de vérité, il faut vérifier si les bits de poids fort sont à 0, suivi par un 1, les bits de poids faible après le 1 étant oubliées. Pour le bit de validité, il suffit de faire un OU entre toutes les entrées. Les deux dernières équations se simplifient en : : <math>S0 = E3 + (\overline{E2} . E1)</math> : <math>S1 = E3 + E2</math>, Le circuit obtenu est le suivant : [[File:Pr encoder 4x2.png|centre|vignette|upright=1.5|Encodeur à priorité 4 vers 2.]] La table de vérité d'un encodeur à priorité 8 vers 3 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. [[File:Encoder.JPG|centre|vignette|upright=2|Table de vérité d'un encodeur à priorité 8 vers 3.]] Utiliser la table de vérité a des défauts. Premièrement, ce n'est pas la meilleure des solutions pour des circuits avec un grand nombre d'entrée. Faire cela donne des tables de vérité rapidement importantes, mêmes pour des encodeurs avec peu de sorties. Le circuit final utilise beaucoup de portes logiques comparé aux autres méthodes. Les solutions alternatives que nous allons voir dans ce qui suit permettent de résoudre ces deux problèmes en même temps. ===Les encodeurs à priorité récursifs=== Une première solution consiste à créer un gros encodeur à base d'encodeurs plus petits.L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Naturellement, il est préférable d'utiliser plusieurs exemplaires d'un même encodeur, c'est à dire que pour une entrée de 256 bits, il vaut mieux utiliser soit deux décodeurs 7 vers 128, soit quatre décodeurs 6 vers 64, etc. La construction est similaire à celle vue dans le chapitre précédent, dans la section sur les encodeurs. La différence est que le OU entre les sorties des encodeurs est remplacé par un multiplexeur. Une version générale est illustrée ci-dessous. On voit que les encodeurs ont une sortie de résultat de X bits notée idx et une sortie de validité notée vld. La sortie de validité finale se calcule en combinant les sorties de validité de chaque encodeur. La sortie est par définition à 1 tant qu'un seul encodeur a une sortie non-nulle, donc quand un seul encodeur a un bit de validité à 1. En clair, c'est un simple OU entre les bits de validité. Reste à déterminer la sortie de donnée, celle qui donne la position du 1 de poids fort. On peut dire que si l'on utilise des encodeurs avec N bits de sortie, alors les N bits de poids faible du résultat seront donnés par le premier encodeur avec une sortie non-nulle. Les résultats de chaque encodeur donnent doncles X bits de poids faible, un seul résultat devant être sélectionné. Le résultat à sélectionner est le premier à avoir un résultat non-nul, donc à avoir un bit de validité à 1. En clair, on peut déterminer quel est le bon encodeur, le bon résultat, en analysant les bits de validité. Mieux : d'après ce qui a été dit, on peut deviner que l'analyse réalisée correspond à trouver la position du premier encodeur à avoir un bit de validité à 1. En clair, c'est l'opération réalisée par un encodeur à priorité lui-même. Tout cela permet de déterminer les N bits de poids faible, amis les autres bits, ceux de poids fort, sont encore à déterminer. Pour cela, on peut remarquer que ceux-ci sont eux-même fournit par l'encodeur à priorité qui commande le MUX. [[File:PE-recursion.svg|centre|vignette|upright=2|Construction d'un encodeur à priorité à partir d'encodeur à priorité plus petits.]] Notons qu'avec cette méthode, il est possible, mais pas très intuitif, de fabriquer un encodeur configurable, capable de se comporter soit comme un encodeur de type ''Find Highest Set'', soit de type ''Find First Set''. L'implémentation la plus simple demande de modifier le circuit qui combine les résultats pour qu'il soit configurable et puisse faire les deux opérations à la demande. ===L'encodeur à priorité avec un circuit d'isolation du 1 de poids fort/faible=== Une autre solution part d'un encodeur normal, auquel on ajoute un circuit qui se charge de sélectionner un seul des bits passé sur son entrée. Le circuit de gestion des priorités a pour fonction de trouver sélectionner un bit et de mettre les autres 1 à 0. Suivant le circuit de priorité considéré, le bit sélectionné est soit le 1 de poids fort, soit le 1 de poids faible. Dans certains cas, le circuit de priorité est configurable et peut trouver l'un ou l'autre suivant ce qu'on lui demande. Dans ce qui va suivre, nous allons partir du principe que l'on souhaite avoir un encodeur qui trouve le 1 de poids fort, sauf indication contraire. [[File:Encodeur à priorité.png|centre|vignette|upright=2|Encodeur à priorité.]] Une méthode assez pratique découpe le circuit de gestion des priorité en petites briques de bases, reliées les unes à la suite des autres. L'idée est que les briques de base sont connectées de manière à propager un signal de mise à zéro. Si une brique détecte un 1, elle envoie un signal aux briques précédentes/suivantes, qui leur dit de mettre leur sortie à zéro. Ce faisant, une fois le premier 1 trouvé, on est certain que les autres bits précédents/suivants sont mis à zéro. Suivant les connexions des briques de base, on peut obtenir soit un encodeur qui effectue l'opération ''Find First Set'', soit encodeur de type ''Find Highest Set'' et réciproquement. En fait, suivant que les briques soient reliées de droite à gauche ou de gauche à droite, on obtiendra l'un ou l'autre de ces deux encodeurs. [[File:Circuit de gestion des priorités.png|centre|vignette|upright=2|Circuit de gestion des priorités.]] Chaque brique de base peut soit recopier le bit en entrée, soit le mettre à zéro. Pour décider quoi faire, elle regarde le signal d'entrée RAZ (''Remise A Zéro''). Si le bit RAZ vaut 1, la sortie est mise à zéro automatiquement. Dans le cas contraire, le bit passé en entrée est recopié. De plus, chaque brique de base doit fournir un signal de remise à zéro RAZ à destination de la brique suivante. Ce signal RAZ de sortie est mis à 1 dans deux cas : soit si le bit d'entrée vaut, soit quand le signal d'entrée RAZ est à 1. Si vous cherchez à la concevoir à partir d'un table de vérité, vous obtiendrez ceci : {| |[[File:Brique de base du circuit de gestion des priorités d'un encodeur à priorité.png|vignette|Brique de base du circuit de gestion des priorités d'un encodeur à priorité.]] |[[File:Circuit de gestion des priorité - Circuit de la brique de base.png|vignette|upright=1.5|Circuit de gestion des priorité - Circuit de la brique de base.]] |} Le circuit complet d'un encodeur à priorité peut être déduit facilement à partir des raisonnements précédents. Après quelques simplifications, on peut obtenir le circuit suivant. On voit qu'on a ajouté une ligne de briques RAZ à l'encodeur 8 vers 3 vu plus haut. [[File:Koder priorytetowy.jpg|centre|vignette|upright=2|Encodeur à priorités]] Le défaut de cette méthode est que le circuit de gestion des priorité est assez lent. Dans le pire des cas, le signal de remise à zéro traverse toutes les briques de base, soit autant qu'il y a de bits d'entrée. Si chaque brique de base met un certain temps, le temps mis pour que le circuit de priorité fasse son travail est proportionnel au nombre de bits de l'entrée. Cela n'a l'air de rien, mais cela peut prendre un temps rédhibitoire pour les circuits de haute performance, destinés à fonctionner à haute fréquence. Pour ces circuits, on préfère que le temps de calcul soit proportionnel au logarithme du nombre de bits d'entrée, un temps proportionnel étant considéré comme trop lent, surtout pour des opérations simples comme celles étudiées ici. Une version légèrement différente de ce circuit est utilisée dans le processeur ARM1, un des tout premiers processeur ARM. L'encodeur à priorité était bidirectionnel, à savoir capable de déterminer soit la place du 1 de poids faible, soit du 1 de poids fort. Pour ceux qui veulent en savoir plus, et qui ont déjà un bagage solide en architecture des ordinateurs, voici un lien à ce sujet : : [https://www.righto.com/2016/01/more-arm1-processor-reverse-engineering.html More ARM1 processor reverse engineering: the priority encoder ] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les circuits de masquage | prevText=Les circuits de masquage | next=Les circuits incrémenteurs/décrémenteurs | nextText=Les circuits incrémenteurs/décrémenteurs }} </noinclude> nid8ghhyexaeli8nm2qiyfvzwugu9tm 768493 768492 2026-06-24T16:05:21Z Mewtow 31375 /* La table de vérité d'un décodeur */ 768493 wikitext text/x-wiki Dans les chapitres précédents, nous avons vu comment fabriquer des circuits relativement généraux. Il est maintenant temps de voir quelques circuits relativement simples, très utilisés. Ces circuits simples sont utilisés pour construire des circuits plus complexes, comme des processeurs, des mémoires, et bien d'autres. Les prochains chapitres vont se concentrer exclusivement sur ces circuits simples, mais courants. Nous allons donner quelques exemples de circuits assez fréquents dans un ordinateur et voir comment construire ceux-ci avec des portes logiques. Dans ce chapitre, nous allons nous concentrer sur quelques circuits, que j'ai décidé de regrouper sous le nom de '''circuits de sélection'''. Les circuits que nous allons présenter sont utilisés dans les mémoires, ainsi que dans certains circuits de calcul. Il est important de bien mémoriser ces circuits, ainsi que la procédure pour les concevoir : nous en aurons besoin dans la suite du cours. Ils sont au nombre de quatre : le décodeur, l'encodeur, le multiplexeur et le démultiplexeur. ==Le décodeur== [[File:DECODER 3 vers 8.png|vignette|Décodeur à 3 entrées et 8 sorties.]] Le premier circuit que nous allons voir est le '''décodeur''', un composant qui contient un grand nombre d'entrées et de sorties, avec des sorties qui sont numérotées. Un décodeur possède une entrée sur laquelle on envoie un nombre codé <math>N</math> bits et <math>2^N</math> sorties de 1 bit. Par exemple, un décodeur avec une entrée de 2 bits aura 4 sorties, un décodeur avec une entrée de 3 bits aura 8 sorties, un décodeur avec une entrée de 8 bits aura 256 sorties, etc. Généralement, on précise le nombre de bits d'entrée et de sortie comme suit : on parle d'un décodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 3 vers 8 pour un décodeur à 3 bits d'entrée et 8 de sortie, de décodeur 4 vers 16, etc. Le fonctionnement d'un décodeur est très simple : il prend sur son entrée un nombre entier x codé en binaire, puis il positionne à 1 la sortie numérotée x et met à zéro toutes les autres sorties. Par exemple, si on envoie la valeur 6 sur ses entrées, il mettra la sortie numéro 6 à 1 et les autres à zéro. Pour résumer, un décodeur est un circuit : * avec une entrée de <math>N</math> bits ; * avec <math>2^N</math> sorties de 1 bit ; * où les sorties sont numérotées en partant de zéro ; * où on ne peut sélectionner qu'une seule sortie à la fois : une seule sortie devra être placée à 1, et toutes les autres à zéro ; * et où deux nombres d'entrée différents devront sélectionner des sorties différentes : la sortie de notre contrôleur qui sera mise à 1 sera différente pour deux nombres différents placés sur son entrée. Une autre manière d'expliquer leur fonctionnement est qu'il traduisent un nombre encodé en binaire vers la représentation ''one-hot''. Pour rappel, sur cette dernière, le nombre N est encodé en mettant le énième bit à 1, les autres sont à 0. Le bit de poids faible compte pour le zéro. Les décodeurs sont très utilisés, au point que faire la liste de leurs utilisations serait bien trop long. Par contre, on peut d'or et déjà prévenir que les décodeurs sont utilisés dans toutes les mémoires RAM et ROM, présentes dans tout ordinateur. La RAM de votre ordinateur contient un ou plusieurs décodeurs, idem pour la mémoire caché intégrée dans le processeur, etc. C'est donc un circuit absolument primordial à étudier, qui reviendra souvent dans ce cours. ===La table de vérité d'un décodeur=== Au vu de ce qui vient d'être dit, on peut facilement écrire la table de vérité d'un décodeur. Pour l'exemple, prenons un décodeur 2 vers 4, pour simplifier la table de vérité. Voici sa table de vérité complète, c’est-à-dire qui contient toutes les sorties regroupées : {|class="wikitable" |- ! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || || 1 || 0 || 0 || 0 |- | 0 || 1 || || 0 || 1 || 0 || 0 |- | 1 || 0 || || 0 || 0 || 1 || 0 |- | 1 || 1 || || 0 || 0 || 0 || 1 |} Vous remarquerez que la table de vérité est assez spéciale. Les seuls bits à 1 sont sur la diagonale. Et cela ne vaut pas que dans l'exemple choisit, mais cela se généralise pour tous les décodeurs. Sur chaque ligne, il n'y a qu'un seul bit à 1, ce qui traduit le fait qu'une entrée ne met qu'une seule sortie est à 1 et met les autres à 0. Si on traduit la table de vérité sous la forme d'équations logiques et de circuit, on obtient ceci : [[File:Decoder Example.svg|centre|vignette|upright=2|Equations logiques et circuit d'un décodeur 2 vers 4.]] [[File:2to4demux.svg|centre|vignette|upright=2|Démultiplexeur à deux sorties.]] Il y a des choses intéressantes à remarquer sur les équations logiques. Pour rappel, l'équation logique d'une sortie est composée, dans le cas général, soit d'un minterm unique, soit d'un OU entre plusieurs minterms. Chaque minterm est l'équation d'un circuit qui compare l'entrée à un nombre bien précis et dépendant du minterm. Si on regarde bien, l'équation de chaque sortie correspond à un minterm et à rien d'autre, il n'y a pas de OU entre plusieurs minterms. Les minterms sont de plus différents pour chaque sortie et on ne trouve pas deux sorties avec le même minterm. Enfin, chaque minterm possible est présent : X bits d'entrée nous donnent 2^X entrées différentes possibles, donc 2^X minterms possibles. Et il se trouve que tous ces minterms possibles sont représentés dans un décodeur, ils ont tous leur sortie associée. C'est une autre manière de définir un décodeur : toutes ses sorties codent un minterm, deux sorties différentes ont des minterms différents et tous les minterms possibles sur n bits sont représentés. Ces informations vont nous être utiles pour la suite. En effet, grâce à elles, nous allons en déduire une méthode générale pour fabriquer un décodeur, peu importe son nombre de bits d'entrée et de sortie. Mais elles permettent aussi de montrer que l'on peut créer n'importe quel circuit combinatoire quelconque à partir d'un décodeur et de quelques portes logiques. Dans ce qui suit, on suppose que le circuit combinatoire en question a une entrée de n bits et une seule sortie de 1 bit. Pour rappel, ce genre de circuit se conçoit en utilisant une table de vérité qu'on traduit en équations logiques, puis en circuits. Le circuit obtenu est alors soit un simple minterm, soit un OU entre plusieurs minterms. Or, le décodeur contient tous les minterms possibles pour une entrée de n bits, avec un minterm par sortie. Il suffit donc de prendre une porte OU et de la connecter aux minterms/sorties adéquats. [[File:Conception d'un circuit combinatoire quelconque à partir d'un décodeur.jpg|centre|vignette|upright=2|Conception d'un circuit combinatoire quelconque à partir d'un décodeur.]] Fabriquer un circuit combinatoire avec un décodeur gaspille pas mal de portes logiques. En effet, le décodeur fournit tous les minterms possibles, alors que seule une minorité est réellement utilisée pour fabriquer le circuit combinatoire. Les minterms en trop correspondent à des paquets de portes NON et ET reliées entre elles, qui ne servent à rien. De plus, les minterms ne sont pas simplifiés. On ne peut pas utiliser les techniques vues dans les chapitres précédents pour simplifier les minterms et réduire le nombre de portes logiques utilisées. Le décodeur reste tel qu'il est, avec l'ensemble des minterms non-simplifiés. Mais la simplicité de conception du circuit reste un avantage dans certaines situations. Notamment, les circuits avec plusieurs bits de sortie sont faciles à fabriquer, notamment si les sorties partagent des minterms (si un minterm est présent dans l'équation de plusieurs sorties différentes, l'usage d'un décodeur permet de facilement factoriser celui-ci). Ceci étant dit, passons à la conception d'un décodeur avec des portes logiques. ===L'intérieur d'un décodeur=== On vient de voir que chaque sortie d'un décodeur correspond à son propre minterm, et que tous les minterms possibles sont représentés. Rappelons que chaque minterm est associé à un circuit qui compare l'entrée à une constante X, X dépendant du minterm. En combinant ces deux informations, on devine qu'un décodeur est simplement composé de comparateurs avec une constante que de minterms/sorties. Par exemple, si je prends un décodeur 7 vers 128, cela veut dire qu'on peut envoyer en entrée un nombre codé entre 0 et 127 et que chaque nombre aura son propre minterm associé : il y aura un minterm qui vérifie si l'entrée vaut 0, un autre vérifie si elle vaut 1, un autre qui vérifie si elle vaut 2, ... , un minterm qui vérifie si l'entrée vaut 126, et enfin un minterm qui vérifie si l'entrée vaut 127. Pour reformuler d'une manière bien plus simple, on peut voir les choses comme suit. Si l'entrée du décodeur vaut N, la sortie mise à 1 est la sortie N. Bref, déduire quand mettre à 1 la sortie N est facile : il suffit de comparer l'entrée avec N. Si l'adresse vaut N, on envoie un 1 sur la sortie, et on envoie un zéro sinon. Pour cela, j'ai donc besoin d'un comparateur pour chaque sortie, et le tour est joué. Précisons cependant que cette méthode gaspille beaucoup de circuits et qu'il y a une certaine redondance. En effet, les comparateurs ont souvent des portions de circuits qui sont identiques et ne diffèrent parfois que ce quelques portes logiques. En utilisant des comparateurs séparés, ces portions de circuits sont dupliquées, alors qu'il serait judicieux de partager. [[File:Internals of decoder.png|centre|vignette|upright=1.5|Exemple d'un décodeur à 8 sorties.]] Comme autre méthode, plus économe en circuits, on peut créer un décodeur en assemblant plusieurs décodeurs plus simples, nommés sous-décodeurs. Ces sous-décodeurs sont des décodeurs normaux, auxquels on a ajouté une entrée RAZ, qui permet de mettre à zéro toutes les sorties : si on met un 0 sur cette entrée, toutes les sorties passent à 0, alors que le décodeur fonctionne normalement sinon. Construire un décodeur demande suffisamment de sous-décodeurs pour combler toutes les sorties. Si on utilise des sous-décodeurs à n entrées, ceux-ci prendront en entrée les n bits de poids faible de l'entrée du décodeur que l'on souhaite construire (le décodeur final). Dans ces conditions, les n décodeurs auront une de leurs sorties à 1. Pour que le décodeur final se comporte comme il faut, il faut désactiver tous les sous-décodeurs, sauf un avec l'entrée RAZ. Pour commander les n bits RAZ des sous-décodeurs, il suffit d'utiliser un décodeur qui est commandé par les bits de poids fort du décodeur final. [[File:Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.jpg|centre|vignette|upright=1.5|Décodeur 3 vers 8 conçu à partir de décodeurs 2 vers 4.]] ==Le démultiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Un démultiplexeur a plusieurs sorties et une seule entrée. Les sorties sont numérotées de 0 à la valeur maximale. Il permet de sélectionner une sortie et de recopier l'entrée dessus, les autres sorties sont mises à 0. Pour séléctionner la sortie, le démultiplexeur possède une entrée de commande, sur laquelle on envoie le numéro de la sortie de destination. Comme le nom l'indique, le démultiplexeur fait l'exact inverse du multiplexeur, que nous verrons plus bas. ===Le démultiplexeur à deux sorties=== Le démultiplexeur le plus simple est le démultiplexeur à deux sorties. Il possède une entrée de donnée, une entrée de commande et deux sorties, toutes de 1 bit. Suivant la valeur du bit sur l'entrée de commande, il recopie le bit d'entrée, soit sur la première sortie, soit sur la seconde. Les deux sorties sont numérotées respectivement 0 et 1. [[File:Demultiplexer.png|centre|vignette|upright=1.5|Démultiplexeur à 2 sorties.]] On peut le concevoir facilement en partant de sa table de vérité. {|class="wikitable" |- ! Entrée de commande ''Select'' ! Entrée de donnée ''Input'' ! ! Sortie 1 ! Sortie 0 |- | 0 || 0 || || 0 || 0 |- |- | 0 || 1 || || 0 || 1 |- |- | 1 || 0 || || 0 || 0 |- |- | 1 || 1 || || 1 || 0 |- |} ===Les démultiplexeurs à plus de deux sorties=== Il est parfaitement possible de créer des démultiplexeurs en utilisant les méthodes du chapitre sur les circuits combinatoires, comme ma méthode des ''minterms'' ou les tableaux de Karnaugh. On obtient alors un démultiplexeur assez simple, composé de deux couches de portes logiques : une couche de portes NON et une couche de portes ET à plusieurs entrées. [[File:Demux.PNG|centre|vignette|upright=1.5|Démultiplexeur fabriqué avec une table de vérité.]] Mais cette méthode n'est pas pratique, car elle utilise beaucoup de portes logiques et que les portes logiques avec beaucoup d'entrées sont difficiles à fabriquer. Pour contourner ces problèmes, il est possible de créer des démultiplexeurs en assemblant des démultiplexeurs 1 vers 2. Évidemment, le même principe s'applique à des démultiplexeurs plus complexes : il suffit de rajouter des couches. [[File:Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.jpg|centre|vignette|upright=1.5|Circuit d'un démultiplexeur à 4 sorties, conçu à partir de démultiplexeurs à 2 sorties.]] Un démultiplexeur peut aussi se fabriquer en utilisant un décodeur et quelques portes ET. Pour comprendre pourquoi, regardons la table de vérité d'un démultiplexeur à quatre sorties. Si vous éliminez le cas où l'entrée de donnée ''Input'' vaut 0, et que vous tenez compte uniquement des entrées de commande, vous retombez sur la table de vérité d'un décodeur. Cela correspond aux cases en rouge. {|class="wikitable" |- ! Input !! E0 !! E1 !! !! S0 !! S1 !! S2 !! S3 |- | 0 || 0 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 0 || 1 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 0 || || 0 || 0 || 0 || 0 |- | 0 || 1 || 1 || || 0 || 0 || 0 || 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 || || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |} En réalité, Le fonctionnement d'un démultiplexeur peut se résumer comme suit : soit l'entrée ''Input'' est à 1 et il fonctionne comme un décodeur dont l'entrée est l'entrée de commande, soit l'entrée ''Input'' vaut 0 et sa sortie est mise à 0. On devine donc qu'il faut combiner un décodeur avec le circuit de mise à zéro vu dans le chapitre précédent. On devine rapidement que l'entrée ''Input'' commande la mise à zéro de la sortie, ce qui donne le circuit suivant : [[File:Démultiplexeur conçu à partir d'un décodeur.jpg|centre|vignette|upright=1.5|Démultiplexeur conçu à partir d'un décodeur.]] ==Le multiplexeur== Les décodeurs ont des cousins : les multiplexeurs et les démultiplexeurs. Les multiplexeurs sont des composants qui possèdent un nombre variable d'entrées, mais une seule sortie. Un multiplexeur permet de sélectionner une entrée et de recopier son contenu sur sa sortie, les entrées non-sélectionnées étant ignorées. Sélectionner l'entrée à recopier sur la sortie se fait en configurant une entrée de commande du multiplexeur. Les entrées sont numérotées de 0 à la valeur maximale. Configurer l'entrée de commande demande juste d'envoyer le numéro de l'entrée sélectionnée dessus. [[File:4-to-1 multiplexer.svg|centre|vignette|Multiplexeur à 4 entrées.]] Les multiplexeurs sont très utilisés et on en retrouve partout : dans les mémoires RAM, dans les processeurs, dans les circuits de calcul, dans les circuits pour communiquer avec les périphériques, et j'en passe. Il s'agit d'un composant très utilisé, qu'il est primordial de bien comprendre avant de passer à la suite du cours. ===Le multiplexeur à deux entrées=== Le multiplexeur le plus simple est le multiplexeur à deux entrées et une sortie. Il est facile de le construire avec des portes logiques, dans les implémentations les plus simples. Sachez toutefois que les multiplexeurs utilisés dans les ordinateurs récents ne sont pas forcément fabriqués avec des portes logiques, mais qu'on peut aussi les fabriquer directement avec des transistors. [[File:Multiplexeur à deux entrées - symbole.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - symbole.]] Pour commencer, établissons sa table de vérité. On va supposer qu'un 0 sur l'entrée de commande sélectionne l'entrée a. La table de vérité devrait être la suivante : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||0||0||0 |- |0||0||1||0 |- |0||1||0||1 |- |0||1||1||1 |- |1||0||0||0 |- |1||0||1||1 |- |1||1||0||0 |- |1||1||1||1 |} Sélectionnons les lignes qui mettent la sortie à 1 : {|class="wikitable" |- !Entrée de commande !Entrée a !Entrée b !Sortie |- |0||1||0||1 |- |0||1||1||1 |- |1||0||1||1 |- |1||1||1||1 |} On sait maintenant quels comparateurs avec une constante utiliser. On peut, écrire l'équation logique du circuit. La première ligne donne l'équation suivante : <math>\overline{E_c} . a . \overline{b}</math>, la seconde donne l'équation <math>\overline{E_c} . a . b</math> , la troisième l'équation <math>E_c . \overline{a} . b</math> et la quatrième l'équation <math>E_c . a . b</math>. L'équation finale obtenue est donc : : <math>(\overline{E_c} . a . \overline{b}) + (\overline{E_c} . a . b) + (E_c . \overline{a} . b) + E_c . a . b</math> L'équation précédente est assez compliquée, mais il y a moyen de la simplifier assez radicalement. Pour cela, nous allons utiliser les règles de l’algèbre de Boole. Pour commencer, nous allons factoriser <math>(\overline{E_c}</math> et <math>E_c</math> : : <math> \left[ \overline{E_c} .[ (a . \overline{b}) + (a . b)] \right] + \left[ E_c . [(\overline{a} . b) + (a . b)] \right] </math> Ensuite, factorisons <math>a</math> dans le premier terme et <math>b</math> dans le second : : <math> \left[ \overline{E_c} . a . (\overline{b} + b) \right] + \left[ E_c . b . (\overline{a} + a) \right]</math> Les termes <math>\overline{b} + b</math> et <math>\overline{a} + a</math> valent 1 : : <math> \left[ \overline{E_c} . a . 1 \right] + \left[ E_c . b . 1 \right]</math> On sait que <math>a . 1 = a</math>, ce qui fait que l'équation simplifiée est la suivante : : <math>(\overline{E_c} . a) + (E_c . b)</math> Le circuit qui correspond est : [[File:Multiplexeur à deux entrées - circuit.png|centre|vignette|upright=1.5|Multiplexeur à deux entrées - circuit.]] ===Les multiplexeurs à plus de deux entrées=== Il est possible de concevoir un multiplexeur quelconque à partir de sa table de vérité. Le résultat est alors un circuit composé d'une porte OU à plusieurs entrées, de plusieurs portes ET, et de quelques portes NON. Un exemple est illustré ci-dessous. Vous remarquerez cependant que ce circuit a un défaut : la porte OU finale a beaucoup d'entrées, ce qui pose de nombreux problèmes techniques. Il est difficile de concevoir des portes logiques avec un très grand nombre d'entrées. Aussi, les applications à haute performance demandent d'utiliser d'autres solutions. [[File:Mux2.png|centre|vignette|upright=1.5|Multiplexeur conçu à partir de sa table de vérité.]] Une solution alternative conçoit un multiplexeur à plus de deux entrées en combinant des multiplexeurs plus simples. Par exemple, en prenant deux multiplexeurs plus simples, et en ajoutant un multiplexeur 2 vers 1 sur leurs sorties respectives. Le multiplexeur final se contente de sélectionner une sortie parmi les deux sorties des multiplexeurs précédents, qui ont déjà effectué une sorte de présélection. [[File:Multiplexeur conçu à partir de multiplexeurs plus simples.jpg|centre|vignette|upright=1.5|Multiplexeur conçu à partir de multiplexeurs plus simples.]] Il existe toutefois une manière bien plus simple pour créer un multiplexeur, qui utilise des opérations de masquage. L'idée est qu'un multiplexeur sélectionne un bit bien précis dans l'opérande. L'idée est de masquer les bits non-sélectionnés, puis de regarder le résultat après masquage. Par défaut, les bits non-sélectionnés sont mis à 0. Le résultat après masquage dépend de la valeur du bit sélectionné : * Si le bit sélectionné vaut 0, alors tous les bits après masquage sont à zéro. * Si le bit sélectionné vaut 1, alors seul un bit du résultat après masquage est à 1. Pour savoir si au moins un bit du résultat vaut 1, l'idée est d'utiliser une porte OU. Si tous les bits sont à 0, la porte OU donnera un zéro. Sinon, elle sortira un 1. La sortie du multiplexeur s'obtient donc en faisant un OU logique entre tous les bits du résultat après masquage. Le circuit au complet est donc composé d'un circuit de masquage, d'un circuit qui génère le masque, et d'une porte OU. Le circuit qui génère le masque transforme le numéro du bit en un masque adéquat. Si le numéro du bit est de N, le masque a son énième bit à 1, les autres à 0. Pour le dire autrement, il convertit le numéro du bit en sa représentation ''one-hot''. Et ce n'est ni plus ni moins que ce que fait un décodeur ! La génération du masque est donc le fait d'un décodeur. [[File:Multiplexeur 2 vers 4 conçu à partir d'un décodeur.png|centre|vignette|upright=2|Multiplexeur 2 vers 4 conçu à partir d'un décodeur]] Le circuit précédent n'est cependant pas parfaitement optimisé. En effet, le décodeur est composé d'une couche de portes NON et d'une couche de portes ET. Il est possible de fusionner les portes ET du décodeur, avec celles liée au masque. Le résultat est une simplification qui élimine quelques portes logiques. Voici ce que cela donne pour un multiplexeur 4 vers 1 : [[File:4to1mux.png|centre|vignette|upright=2|Multiplexeur 2 vers 4, conçu à partir d'un décodeur, simplifié pour éviter des redondances.]] ==L'encodeur== [[File:8 to 3 simple encoder IEC symbol.svg|vignette|upright=0.5|Encodeur à 8 entrées (et 3 sorties).]] Il existe un circuit qui fait exactement l'inverse du décodeur : c'est l''''encodeur'''. Là où les décodeurs ont une entrée de <math>N</math> bits et <math>2^N</math> sorties de 1 bit, l'encodeur a à l'inverse <math>2^N</math> entrées de 1 bit avec une sortie de <math>N</math> bits. Par exemple, un encodeur avec une entrée de 4 bits aura 2 sorties, un décodeur avec une entrée de 8 bits aura 3 sorties, un décodeur avec une entrée de 256 bits aura 8 sorties, etc. Comme pour les décodeurs, on parle d'un encodeur X vers Y pour X bits d'entrée et Y de sortie. Ce qui fait qu'on peut parler de décodeur 8 vers 3 pour un décodeur à 8 bits d'entrée et 3 de sortie, de décodeur 16 vers 4, etc. [[File:Encoder block diagram.jpg|centre|vignette|upright=1.5|Entrées et sorties d'un encodeur.]] De plus, contrairement au décodeur, ce sont les entrées qui sont numérotées de 0 à N et non les sorties. Dans ce qui suit, on va supposer qu'une seule des entrées est à 1. Il existe des encodeurs capables de traiter le cas où plusieurs bits d'entrée sont à 1, qui sont appelés des encodeurs à priorité, mais nous les laissons pour le chapitre suivant. Le chapitre suivant sera totalement dédié aux encodeurs à priorité, aussi nous préférons nous focaliser sur le cas d'un encodeur simple, capable de traiter uniquement le cas où une seule entrée est à 1. En sortie, l'encodeur donne le numéro de l'entrée qui est à 1. Par exemple, si l'entrée numéro 5 est à 1 et les autres à 0, alors l'encodeur envoie un 5 sur sa sortie. Une autre manière d'expliquer son fonctionnement est la suivant : un encodeur traduit un nombre codé en représentation ''one-hot'' vers du binaire normal. L'utilité d'un encodeur n'est pas très évidente à ce moment du cours, mais nous pouvons déjà dire qu'ils seront utiles dans certaines formes de mémoires RAM appelées des mémoires associatives, qui sont utilisées dans des routeurs, switchs et autre matériel réseau. La majorité des mémoires caches de nos ordinateurs sont de ce type, bien que leur implémentation exacte ne fasse pas usage d'un encodeur. Une autre utilisation est la transformation d'un nombre codé en représentation ''one-hot'' vers du binaire normal, chose marginalement utile. ===L'encodeur 4 vers 2=== Prenons l'exemple d'un encodeur à 4 entrées et 2 sorties. Écrivons sa table de vérité. D'après la description du circuit, on devrait trouver ceci : {|class="wikitable" |+ Table de vérité d'un encodeur 4 vers 2 |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 |- | 0 || 0 || 0 || 1 || || 0 || 0 |- | 0 || 0 || 1 || 0 || || 0 || 1 |- | 0 || 1 || 0 || 0 || || 1 || 0 |- | 1 || 0 || 0 || 0 || || 1 || 1 |} Vous voyez que la table de vérité est incomplète. En effet, l'encodeur fonctionne tant qu'une seule de ses entrées est à 1. L'encodeur dit alors quelle est la sortie à 1, mais cela suppose que les autres soient à 0. Si plusieurs entrées sont à 1, le comportement de l'encodeur est potentiellement erroné. En effet, il donnera un résultat incorrect sur certaines entrées. Mais passons cela sous silence et ne tenons compte que de la table de vérité partielle précédente. On peut traduire cette table de vérité en circuit logique. On obtient alors les équations suivantes : : <math>S1 = E3 + E2</math> : <math>S0 = E3 + E1</math> Le tout donne le circuit suivant : [[File:A Simple 4-2 encoder using or gate.jpg|centre|vignette|upright=1.5|Exemple d'encodeur à 4 entrées et 2 sorties.]] ===Les encodeurs à plus de deux sorties=== Il est possible de créer un encodeur complexe en combinant plusieurs encodeurs simples. C'est un peu la même chose qu'avec les décodeurs, pour lesquels on peut créer un décodeur 8 vers 256 à base de deux décodeurs 7 vers 128, ou de quatre décodeurs 6 vers 64. L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Pour comprendre l'idée, prenons la table de vérité d'un encodeur 8 vers 3; donnée dans le tableau ci-dessous. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 0 || 1 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 0 || 0 || 0 || 1 || 0 || || 0 || 0 || 1 |- | 0 || 0 || 0 || 0 || 0 || 1 || 0 || 0 || || 0 || 1 || 0 |- | 0 || 0 || 0 || 0 || 1 || 0 || 0 || 0 || || 0 || 1 || 1 |- | 0 || 0 || 0 || 1 || 0 || 0 || 0 || 0 || || 1 || 0 || 0 |- | 0 || 0 || 1 || 0 || 0 || 0 || 0 || 0 || || 1 || 0 || 1 |- | 0 || 1 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 0 |- | 1 || 0 || 0 || 0 || 0 || 0 || 0 || 0 || || 1 || 1 || 1 |} En regardant bien, vous verrez que vous pouvez trouver la table de vérité d'un encodeur 4 vers 2 en deux exemplaires, indiquées en rouge. {|class="wikitable" |+ Table de vérité d'un encodeur 8 vers 3 |- ! E7 !! E6 !! E5 !! E4 !! E3 !! E2 !! E1 !! E0 !! !! S2 !! S1 !! S0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | 0 || 0 || 0 || 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 |- | bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 |- | bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || bgcolor="#FF0000" | 0 || 0 || 0 || 0 || 0 || | 1 || bgcolor="#FF0000" | 1 || bgcolor="#FF0000" | 1 |} On voit que les deux bits de poids faibles correspondent à la sortie de l'encodeur activé par l'entrée. Si le premier encodeur est activé, c'est lui qui fournit les bits de poids faibles. Inversement, si c'est le second encodeur qui a un résultat non-nul, c'est lui qui fournit les bits de poids faible. Notons que seul un des deux encodeurs a une sortie non-nulle à la fois : soit le premier a une sortie non-nulle, soit c'est le second, mais c'est impossible que ce soit les deux en même temps. Cela permet de déduire quelle opération permet de mixer les deux résultats : un simple OU logique suffit. Car, pour rappel, 0 OU X donne X, quelque que soit le X en question. Les bits de poids faible du résultat se calculent en faisant un OU entre les deux résultats des encodeurs. Ensuite, il faut déterminer comment fixer le bit de poids fort du résultat. Il vaut 0 si le premier encodeur a une entrée non-nulle, et 1 si c'est le premier encodeur qui a une entrée non-nulle. Pour cela, il suffit de vérifier si les bits de poids forts, associés au premier encodeur, contiennent un 1. Si c'est le cas, alors on met la troisième sortie à 1. [[File:Encodeur fabriqué à partir d'encodeurs plus petits.png|centre|vignette|upright=2|Encodeur fabriqué à partir d'encodeurs plus petits.]] Notons que cette procédure, à savoir faire un OU entre les sorties de deux encodeurs simples, puis faire un OU pour calculer le troisième bit, marche pour tout encodeur de taille quelconque. À vrai dire, le circuit obtenu plus haut d'un encodeur 4 vers 2 est conçu ainsi, mais en combinant deux encodeurs 2 vers 1. La procédure consiste à ajouter trois portes OU à deux encodeurs. Mais ceux-ci sont eux-même composés de portes OU associées à des encodeurs plus petits, et ainsi de suite. On peut poursuivre ainsi jusqu’à tomber sur des encodeurs 4 vers 2, qui sont eux-mêmes composés de deux portes OU. Au final, on se retrouve avec un circuit conçu uniquement à partir de portes OU. Notons qu'il est possible de simplifier le circuit obtenu avec la procédure en fusionnant des portes OU. Si on simplifie vraiment au maximum, le circuit consiste alors en une porte OU à plusieurs entrées par sortie, chacune étant connectée à certaines entrées bien précises. Pour un encodeur 8 vers 3, la simplification du circuit devrait donner ceci : [[File:8-3 Encoder.gif|centre|vignette|upright=1.5|Encodeur 8 vers 3.]] ==L'encodeur à priorité== L''''encodeur à priorité''' est un dérivé du circuit encodeur, vu dans la section précédente. La différence ne se situe pas dans le nombre d'entrée ou de sortie, ni même dans son interface extérieure. Comme pour l'encodeur normal, l'encodeur à priorité possède <math>2^N</math> entrées numérotées de 0 à <math>2^N - 1</math> et N sorties. Une autre manière plus intuitive de le dire est qu'il possède N entrées et <math>\log_2{N}</math> sorties. Pas de changement de ce point de vue. La différence entre encodeur simple et encodeur à priorité tient dans leur fonctionnement, dans le calcul qu'ils font. Avec un encodeur normal, on a supposé que seul un bit d'entrée pouvait être à 1, les autres étant systématiquement à 0. Si cette condition est naturellement remplie dans certains cas d’utilisation, ce n'est pas le cas dans d'autres. L'encodeur à priorité est un encodeur amélioré dans le sens où il donne un résultat valide même quand plusieurs bits d'entrée sont à 1. Il donne donc un résultat pour n'importe quel nombre passé en entrée. Mais avant de passer aux explications, un peu de terminologie utile. Dans ce qui suit, nous aurons à utiliser des expressions du type "le 1 de poids faible", "le 1 de poids fort" et quelques autres du même genre. Quand nous parlerons du 1 de poids faible, nous voudrons parler du premier 1 que l'on croise dans un nombre en partant de sa droite. Par exemple, dans le nombre 0110 1000, le 1 de poids faible est le quatrième bit. Quant au "1 de poids fort", c'est le premier 1 que l'on croise quand on parcourt le nombre à partir de sa gauche. Dans le cas le plus fréquent, l'encodeur à priorité prend en entrée un nombre et donne la position du 1 de poids fort. Mais dans d'autres cas, l'encodeur à priorité donne la position du 1 de poids faible. Il existe des équivalents, mais qui trouvent cette fois-ci les zéros de poids fort/faible, mais nous n'en parlerons pas dans ce chapitre. ===L'encodeur à priorité conçu à partir de sa table de vérité=== Il est possible de concevoir l'encodeur à priorité à partir de sa table de vérité, mais les méthodes des minterms ou des maxterms ne donnent pas de très bons résultats. Notons que ces encodeurs ont souvent une nouvelle entrée notée V, qui indique si la sortie est valide, et qui indique qu'au moins une entrée est à 1. Elle vaut 1 si au moins une entrée est à 1, 0 si toutes les entrées sont à 0. À titre d'exemple, la table de vérité d'un encodeur à priorité 4 vers 2 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. {|class="wikitable" |- ! E3 !! E2 !! E1 !! E0 !! !! S1 !! S0 !! V |- | 0 || 0 || 0 || 0 || || 0 || 0 || 0 |- | 0 || 0 || 0 || 1 || || 0 || 0 || 1 |- | 0 || 0 || 1 || X || || 0 || 1 || 1 |- | 0 || 1 || X || X || || 1 || 0 || 1 |- | 1 || X || X || X || || 1 || 1 || 1 |} Les équations logiques obtenues sont donc les suivantes : : <math>V = E3 + E2 + E1 + E0</math> : <math>S0 = E3 + (\overline{E3} . \overline{E2} . E1)</math> : <math>S1 = E3 + ( \overline{E3} . E2 )</math> On voit quelle est la logique de chaque équation. Pour chaque ligne de la table de vérité, il faut vérifier si les bits de poids fort sont à 0, suivi par un 1, les bits de poids faible après le 1 étant oubliées. Pour le bit de validité, il suffit de faire un OU entre toutes les entrées. Les deux dernières équations se simplifient en : : <math>S0 = E3 + (\overline{E2} . E1)</math> : <math>S1 = E3 + E2</math>, Le circuit obtenu est le suivant : [[File:Pr encoder 4x2.png|centre|vignette|upright=1.5|Encodeur à priorité 4 vers 2.]] La table de vérité d'un encodeur à priorité 8 vers 3 est illustré ci-dessous. Le signe X signifie que le bit peut prendre la valeur 0 ou 1 sans que cela change quoique ce soit à l'entrée. [[File:Encoder.JPG|centre|vignette|upright=2|Table de vérité d'un encodeur à priorité 8 vers 3.]] Utiliser la table de vérité a des défauts. Premièrement, ce n'est pas la meilleure des solutions pour des circuits avec un grand nombre d'entrée. Faire cela donne des tables de vérité rapidement importantes, mêmes pour des encodeurs avec peu de sorties. Le circuit final utilise beaucoup de portes logiques comparé aux autres méthodes. Les solutions alternatives que nous allons voir dans ce qui suit permettent de résoudre ces deux problèmes en même temps. ===Les encodeurs à priorité récursifs=== Une première solution consiste à créer un gros encodeur à base d'encodeurs plus petits.L'idée de découper le nombre d'entrée en morceaux séparés, chaque morceau étant traité par un encodeur à priorité distinct des autres. Les résultats des différents encodeurs sont ensuite combinés pour donner le résultat final. Naturellement, il est préférable d'utiliser plusieurs exemplaires d'un même encodeur, c'est à dire que pour une entrée de 256 bits, il vaut mieux utiliser soit deux décodeurs 7 vers 128, soit quatre décodeurs 6 vers 64, etc. La construction est similaire à celle vue dans le chapitre précédent, dans la section sur les encodeurs. La différence est que le OU entre les sorties des encodeurs est remplacé par un multiplexeur. Une version générale est illustrée ci-dessous. On voit que les encodeurs ont une sortie de résultat de X bits notée idx et une sortie de validité notée vld. La sortie de validité finale se calcule en combinant les sorties de validité de chaque encodeur. La sortie est par définition à 1 tant qu'un seul encodeur a une sortie non-nulle, donc quand un seul encodeur a un bit de validité à 1. En clair, c'est un simple OU entre les bits de validité. Reste à déterminer la sortie de donnée, celle qui donne la position du 1 de poids fort. On peut dire que si l'on utilise des encodeurs avec N bits de sortie, alors les N bits de poids faible du résultat seront donnés par le premier encodeur avec une sortie non-nulle. Les résultats de chaque encodeur donnent doncles X bits de poids faible, un seul résultat devant être sélectionné. Le résultat à sélectionner est le premier à avoir un résultat non-nul, donc à avoir un bit de validité à 1. En clair, on peut déterminer quel est le bon encodeur, le bon résultat, en analysant les bits de validité. Mieux : d'après ce qui a été dit, on peut deviner que l'analyse réalisée correspond à trouver la position du premier encodeur à avoir un bit de validité à 1. En clair, c'est l'opération réalisée par un encodeur à priorité lui-même. Tout cela permet de déterminer les N bits de poids faible, amis les autres bits, ceux de poids fort, sont encore à déterminer. Pour cela, on peut remarquer que ceux-ci sont eux-même fournit par l'encodeur à priorité qui commande le MUX. [[File:PE-recursion.svg|centre|vignette|upright=2|Construction d'un encodeur à priorité à partir d'encodeur à priorité plus petits.]] Notons qu'avec cette méthode, il est possible, mais pas très intuitif, de fabriquer un encodeur configurable, capable de se comporter soit comme un encodeur de type ''Find Highest Set'', soit de type ''Find First Set''. L'implémentation la plus simple demande de modifier le circuit qui combine les résultats pour qu'il soit configurable et puisse faire les deux opérations à la demande. ===L'encodeur à priorité avec un circuit d'isolation du 1 de poids fort/faible=== Une autre solution part d'un encodeur normal, auquel on ajoute un circuit qui se charge de sélectionner un seul des bits passé sur son entrée. Le circuit de gestion des priorités a pour fonction de trouver sélectionner un bit et de mettre les autres 1 à 0. Suivant le circuit de priorité considéré, le bit sélectionné est soit le 1 de poids fort, soit le 1 de poids faible. Dans certains cas, le circuit de priorité est configurable et peut trouver l'un ou l'autre suivant ce qu'on lui demande. Dans ce qui va suivre, nous allons partir du principe que l'on souhaite avoir un encodeur qui trouve le 1 de poids fort, sauf indication contraire. [[File:Encodeur à priorité.png|centre|vignette|upright=2|Encodeur à priorité.]] Une méthode assez pratique découpe le circuit de gestion des priorité en petites briques de bases, reliées les unes à la suite des autres. L'idée est que les briques de base sont connectées de manière à propager un signal de mise à zéro. Si une brique détecte un 1, elle envoie un signal aux briques précédentes/suivantes, qui leur dit de mettre leur sortie à zéro. Ce faisant, une fois le premier 1 trouvé, on est certain que les autres bits précédents/suivants sont mis à zéro. Suivant les connexions des briques de base, on peut obtenir soit un encodeur qui effectue l'opération ''Find First Set'', soit encodeur de type ''Find Highest Set'' et réciproquement. En fait, suivant que les briques soient reliées de droite à gauche ou de gauche à droite, on obtiendra l'un ou l'autre de ces deux encodeurs. [[File:Circuit de gestion des priorités.png|centre|vignette|upright=2|Circuit de gestion des priorités.]] Chaque brique de base peut soit recopier le bit en entrée, soit le mettre à zéro. Pour décider quoi faire, elle regarde le signal d'entrée RAZ (''Remise A Zéro''). Si le bit RAZ vaut 1, la sortie est mise à zéro automatiquement. Dans le cas contraire, le bit passé en entrée est recopié. De plus, chaque brique de base doit fournir un signal de remise à zéro RAZ à destination de la brique suivante. Ce signal RAZ de sortie est mis à 1 dans deux cas : soit si le bit d'entrée vaut, soit quand le signal d'entrée RAZ est à 1. Si vous cherchez à la concevoir à partir d'un table de vérité, vous obtiendrez ceci : {| |[[File:Brique de base du circuit de gestion des priorités d'un encodeur à priorité.png|vignette|Brique de base du circuit de gestion des priorités d'un encodeur à priorité.]] |[[File:Circuit de gestion des priorité - Circuit de la brique de base.png|vignette|upright=1.5|Circuit de gestion des priorité - Circuit de la brique de base.]] |} Le circuit complet d'un encodeur à priorité peut être déduit facilement à partir des raisonnements précédents. Après quelques simplifications, on peut obtenir le circuit suivant. On voit qu'on a ajouté une ligne de briques RAZ à l'encodeur 8 vers 3 vu plus haut. [[File:Koder priorytetowy.jpg|centre|vignette|upright=2|Encodeur à priorités]] Le défaut de cette méthode est que le circuit de gestion des priorité est assez lent. Dans le pire des cas, le signal de remise à zéro traverse toutes les briques de base, soit autant qu'il y a de bits d'entrée. Si chaque brique de base met un certain temps, le temps mis pour que le circuit de priorité fasse son travail est proportionnel au nombre de bits de l'entrée. Cela n'a l'air de rien, mais cela peut prendre un temps rédhibitoire pour les circuits de haute performance, destinés à fonctionner à haute fréquence. Pour ces circuits, on préfère que le temps de calcul soit proportionnel au logarithme du nombre de bits d'entrée, un temps proportionnel étant considéré comme trop lent, surtout pour des opérations simples comme celles étudiées ici. Une version légèrement différente de ce circuit est utilisée dans le processeur ARM1, un des tout premiers processeur ARM. L'encodeur à priorité était bidirectionnel, à savoir capable de déterminer soit la place du 1 de poids faible, soit du 1 de poids fort. Pour ceux qui veulent en savoir plus, et qui ont déjà un bagage solide en architecture des ordinateurs, voici un lien à ce sujet : : [https://www.righto.com/2016/01/more-arm1-processor-reverse-engineering.html More ARM1 processor reverse engineering: the priority encoder ] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les circuits de masquage | prevText=Les circuits de masquage | next=Les circuits incrémenteurs/décrémenteurs | nextText=Les circuits incrémenteurs/décrémenteurs }} </noinclude> 1b3biq8am3t0zf8agtiystnzi5ca2q1 Fonctionnement d'un ordinateur/Les bus électroniques 0 72818 768494 765761 2026-06-24T16:13:45Z Mewtow 31375 /* L'arbitrage du bus */ 768494 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les méthodes d'arbitrage (algorithmes)=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Pour donner un exemple d'algorithme d'arbitrage, parlons de l''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre méthode est celle de l''''arbitrage par requête''', qui se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage par ''daisy chain''=== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 8slaie6lvwnf8261rtxlwbhawbf217s 768495 768494 2026-06-24T16:14:09Z Mewtow 31375 /* Les méthodes d'arbitrage (algorithmes) */ 768495 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les méthodes d'arbitrage (algorithmes)=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Pour donner un exemple d'algorithme d'arbitrage, parlons de l''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre méthode est celle de l''''arbitrage par requête''', qui se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage par ''daisy chain''=== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> q0vdikk8mycgxct4hc2pvsrsaw43344 768496 768495 2026-06-24T16:15:57Z Mewtow 31375 /* Les méthodes d'arbitrage (algorithmes) */ 768496 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. ===Le multiplexage temporel=== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage du bus par requête explicite=== L''''arbitrage par requête''' se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage par ''daisy chain''=== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> rng42wtr9ygqxtebofkt0ods2i7yjb4 768497 768496 2026-06-24T16:19:43Z Mewtow 31375 /* L'arbitrage du bus */ 768497 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. ===Le multiplexage temporel=== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage du bus par requête explicite=== L''''arbitrage par requête''' se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage par ''daisy chain''=== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> apn429fjyoqt5e3ud4hpnpuggc24qug 768498 768497 2026-06-24T16:24:32Z Mewtow 31375 /* L'arbitrage du bus */ 768498 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ===Le multiplexage temporel=== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage du bus par requête explicite=== L''''arbitrage par requête''' se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage par ''daisy chain''=== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> t9bsrn93i8s72u049use1r2uhhjoikl 768499 768498 2026-06-24T16:30:41Z Mewtow 31375 /* L'arbitrage du bus */ 768499 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ===Le multiplexage temporel=== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage du bus par requête explicite=== L''''arbitrage par requête''' se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage par ''daisy chain''=== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> dctto6bpfim37jptq8p9wpo579cyyzn 768500 768499 2026-06-24T16:31:20Z Mewtow 31375 /* L'arbitrage du bus */ 768500 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ===Le multiplexage temporel=== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage du bus par requête explicite=== L''''arbitrage par requête''' se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage par ''daisy chain''=== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> s9ee0vpyzs7yqnv4roa5styypjywwz9 768501 768500 2026-06-24T16:31:57Z Mewtow 31375 /* L'arbitrage du bus */ 768501 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====Le multiplexage temporel==== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ====L'arbitrage du bus par requête explicite==== L''''arbitrage par requête''' se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage par ''daisy chain''==== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> iobbhguk6ts2fm8317hqwgtrtj8h5ap 768502 768501 2026-06-24T16:35:11Z Mewtow 31375 /* Les méthodes d'arbitrage de bus les plus communes */ 768502 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requête''' se résume à un simple « premier arrivé, premier servi » ! L'idée est que tout composant peut réserver le bus si celui-ci est libre, mais doit attendre si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage par ''daisy chain''==== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====Le multiplexage temporel==== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> tnt0dz6ndo58winvj1mi20fqcbis6lz 768503 768502 2026-06-24T16:43:46Z Mewtow 31375 /* Les méthodes d'arbitrage de bus les plus communes */ 768503 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage par ''daisy chain''==== Pour donner un exemple d'arbitrage centralisé, nous allons aborder l''''arbitrage par daisy chain'''. Il s'agit d'un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====Le multiplexage temporel==== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> cmo94fx95lpe3sf7uq9p33tj2e5m6ia 768504 768503 2026-06-24T16:44:03Z Mewtow 31375 /* L'arbitrage par daisy chain */ 768504 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====Le multiplexage temporel==== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> mrwlf48ler94gxcfcg0yqiyhizc5p91 768505 768504 2026-06-24T16:47:52Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768505 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une solution simple donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====Le multiplexage temporel==== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 9iwuqwz607ob2r8p54sadqsev53xou7 768506 768505 2026-06-24T17:01:05Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768506 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une solution simple donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====Le multiplexage temporel==== L''''arbitrage par multiplexage temporel'''. Celui-ci peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> hvki7zjnjmnirgiay21yihihok2nnbk 768507 768506 2026-06-24T17:02:48Z Mewtow 31375 /* Les méthodes d'arbitrage de bus les plus communes */ 768507 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une solution simple donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Une autre méthode d'arbitrage donne l'accès au bus à chaque composant à part égale. Elle porte le nom d'''''arbitrage par multiplexage temporel'''. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> hkozzd5pb6ogyqvskez2zfhwnoea2xp 768508 768507 2026-06-24T17:03:44Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768508 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ===Les méthodes d'arbitrage de bus les plus communes=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et sélectionne un candidat. Seul ce candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une méthode d'arbitrage assez basique donne l'accès au bus à chaque composant à part égale. Elle porte le nom d'''''arbitrage par multiplexage temporel'''. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> au6vfgkd0c5u7nkehwlkcpleh2w22xm 768509 768508 2026-06-24T17:06:34Z Mewtow 31375 /* Les méthodes d'arbitrage de bus les plus communes */ 768509 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Pour résumer, le contrôleur de bus décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Le contrôleur de bus sélectionne le ''bus master'' parmi une liste de candidats, qui doivent demander une requête d'accès au bus. Seul un candidat se voit attribuer l'accès au bus, les autres doivent attendre. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une méthode d'arbitrage assez basique donne l'accès au bus à chaque composant à part égale. Elle porte le nom d'''''arbitrage par multiplexage temporel'''. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> ortn87lsclw7b4j3dq7royl0ji0756y 768510 768509 2026-06-24T17:08:32Z Mewtow 31375 /* L'arbitrage centralisé : le contrôleur de bus */ 768510 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une méthode d'arbitrage assez basique donne l'accès au bus à chaque composant à part égale. Elle porte le nom d'''''arbitrage par multiplexage temporel'''. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> o4tojpe3qtm3xyid179kecrcqt2kk8s 768511 768510 2026-06-24T17:08:40Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768511 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une méthode d'arbitrage assez basique donne l'accès au bus à chaque composant à part égale. Elle porte le nom d''''arbitrage par multiplexage temporel'''. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> mqpjzjwu8o15vztphd0jt98crnfuiev 768512 768511 2026-06-24T17:10:20Z Mewtow 31375 /* L'arbitrage par daisy chain */ 768512 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une méthode d'arbitrage assez basique donne l'accès au bus à chaque composant à part égale. Elle porte le nom d''''arbitrage par multiplexage temporel'''. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====L'arbitrage par ''pooling''==== L'arbitrage par ''pooling'' interroge chaque composant un par un, pour savoir s'ils veulent avoir accès au bus. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> gv258eue3ngkq2iljvdwhpz65mrgw49 768513 768512 2026-06-24T17:13:49Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768513 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une méthode d'arbitrage assez basique donne l'accès au bus à chaque composant à part égale. Elle porte le nom d''''arbitrage par multiplexage temporel'''. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. Une autre solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====L'arbitrage par ''pooling''==== L'arbitrage par ''pooling'' interroge chaque composant un par un, pour savoir s'ils veulent avoir accès au bus. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> gy8ui9kb5gvtij9o9nhcgubcgdx8d7c 768514 768513 2026-06-24T17:16:10Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768514 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====L'arbitrage par ''pooling''==== L'arbitrage par ''pooling'' interroge chaque composant un par un, pour savoir s'ils veulent avoir accès au bus. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> h0h70axsclwxnkrqbxr8x7rboyl4xg2 768515 768514 2026-06-24T17:17:19Z Mewtow 31375 /* L'arbitrage par pooling */ 768515 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Une solution donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====L'arbitrage par ''pooling''==== L'arbitrage par ''pooling'' interroge chaque composant un par un, pour savoir s'ils veulent avoir accès au bus. Elle donne l'accès au bus à chaque composant à part égale. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> nzdqgxacz57h8z5287eziz1oh3zgt9g 768517 768515 2026-06-24T17:39:23Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768517 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===L'arbitrage centralisé : le contrôleur de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. Certains protocoles permettent de libérer le bus de force pour laisser la place à un autre composant : on parle alors de '''''bus mastering'''''. Sur certains bus, certains composants sont prioritaires, et les circuits chargés de l'arbitrage libèrent le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est parfois possible de programmer l’algorithme d'arbitrage, si un circuit assez complexe est utilisé comme contrôleur de bus. Il est possible d'utiliser un microcontroleur comme controleur de bus, et tout dépend de la manière dont celui-ci est programmé. Une solution beaucoup plus simple donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ====L'arbitrage par ''pooling''==== L'arbitrage par ''pooling'' interroge chaque composant un par un, pour savoir s'ils veulent avoir accès au bus. Elle donne l'accès au bus à chaque composant à part égale. Elle peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle, durant un temps fixe. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur ! Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. Une solution est d'autoriser à un composant de libérer le bus prématurément, s'il n'en a pas besoin. Ce faisant, les composants qui n'utilisent pas beaucoup le bus laisseront la place aux composants plus gourmands. ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> f1jlwxyxqz76h6h0oyxy8hqhpo1fex2 768518 768517 2026-06-24T18:19:50Z Mewtow 31375 /* L'arbitrage centralisé : le contrôleur de bus */ 768518 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est parfois possible de programmer l’algorithme d'arbitrage, si un circuit assez complexe est utilisé comme contrôleur de bus. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, et tout dépend de la manière dont celui-ci est programmé. Par exemple, il est possible d'utiliser l'arbitrage à part égale. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur, incrémenté lorsqu'un composant libère le bus ! Une solution beaucoup plus simple donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> o2b9kre5ysgj2n4owibcbxsqkj8tplr 768519 768518 2026-06-24T18:20:50Z Mewtow 31375 /* Les différents types d'arbitrage de bus */ 768519 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ====L'arbitrage à priorité versus à part égales==== Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Bref, les méthodes d'arbitrage sont nombreuses. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est parfois possible de programmer l’algorithme d'arbitrage, si un circuit assez complexe est utilisé comme contrôleur de bus. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, et tout dépend de la manière dont celui-ci est programmé. Par exemple, il est possible d'utiliser l'arbitrage à part égale. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur, incrémenté lorsqu'un composant libère le bus ! Une solution beaucoup plus simple donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> o7l4fxlecj6xnf5m29e6zix0fy3hjfl 768520 768519 2026-06-24T18:23:36Z Mewtow 31375 /* L'arbitrage à priorité versus à part égales */ 768520 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ====L'arbitrage à priorité versus à part égales==== Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est parfois possible de programmer l’algorithme d'arbitrage, si un circuit assez complexe est utilisé comme contrôleur de bus. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, et tout dépend de la manière dont celui-ci est programmé. Par exemple, il est possible d'utiliser l'arbitrage à part égale. Son implémentation est très simple : le circuit qui sélectionne le ''bus master'' est un simple compteur, incrémenté lorsqu'un composant libère le bus ! Une solution beaucoup plus simple donne une priorité fixe à chaque composant. Tel composant passe avant tel autre, qui lui-même passe avant tel autre, etc. Le circuit qui sélectionne l'émetteur est alors un simple encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Les signaux ''Grant'' sont généré à partir de la sortie de cet encodeur. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> om9cjkzjfd2fivl0g8yxjywqglu7s0e 768521 768520 2026-06-24T18:27:10Z Mewtow 31375 /* L'arbitrage du bus par requêtes indépendantes */ 768521 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ====L'arbitrage à priorité versus à part égales==== Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 48arjnj78zbcpcji3jbr5xtw4998x86 768522 768521 2026-06-24T18:28:27Z Mewtow 31375 /* L'arbitrage sur les bus à collecteur ouvert */ 768522 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ====L'arbitrage à priorité versus à part égales==== Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 6ao5o8ygc732cu8zgmnevyjldv0nsx1 768523 768522 2026-06-24T18:37:12Z Mewtow 31375 /* L'arbitrage par daisy chain */ 768523 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ====L'arbitrage à priorité versus à part égales==== Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par adressage==== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> q7xe1u5m1n3ame844ceswpbmht6d23l 768524 768523 2026-06-24T18:37:59Z Mewtow 31375 /* L'arbitrage par adressage */ 768524 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ====L'arbitrage à priorité versus à part égales==== Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par adressage==== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par daisy chain'''est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 4pvhzwnxf263t0f92m0thzdfiusprrs 768525 768524 2026-06-24T18:38:10Z Mewtow 31375 /* L'arbitrage par daisy chain */ 768525 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. ====L'arbitrage à priorité versus à part égales==== Une seconde classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par adressage==== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> eaudqxxby35lvjs5im2n45hf2y87k7s 768526 768525 2026-06-24T18:43:58Z Mewtow 31375 /* Les différents types d'arbitrage de bus */ 768526 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage à priorité versus à part égales==== Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par adressage==== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> jajb55hkhvm5tmu54vywici2rr6kyky 768527 768526 2026-06-24T18:44:05Z Mewtow 31375 /* Les différents types d'arbitrage de bus */ 768527 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage à priorité versus à part égales==== Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ====L'arbitrage centralisé et décentralisé==== Une première classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par adressage==== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 09d5uf0d8hniu34w1u83h2j5ld092ql 768528 768527 2026-06-24T18:44:25Z Mewtow 31375 /* L'arbitrage centralisé et décentralisé */ 768528 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage à priorité versus à part égales==== Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ====L'arbitrage centralisé et décentralisé==== Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par adressage==== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 2e5vje2uiz4p7j47cxlhm6w1v052r45 768529 768528 2026-06-24T18:50:25Z Mewtow 31375 /* L'arbitrage décentralisé sur les bus à collecteur ouvert */ 768529 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage à priorité versus à part égales==== Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ====L'arbitrage centralisé et décentralisé==== Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} ===L'arbitrage centralisé : le contrôleur de bus=== Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ====L'arbitrage du bus par requêtes indépendantes==== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ====L'arbitrage par adressage==== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ====L'arbitrage par ''daisy chain''==== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 3ni5tyg57dsuksv71lltsewyntkd0le 768530 768529 2026-06-24T18:51:10Z Mewtow 31375 /* L'arbitrage centralisé : le contrôleur de bus */ 768530 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. ====L'arbitrage à priorité versus à part égales==== Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. ====L'arbitrage centralisé et décentralisé==== Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> fhi0es9fj52owflmh0quzxkzwzrhgu2 768531 768530 2026-06-24T18:51:31Z Mewtow 31375 /* Les différents types d'arbitrage de bus */ 768531 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' qui part des composants et arrive dans l'arbitre, et un fil ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Le composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Le fil est unique ''Request'' est partagé entre tous les composants (cela remplace l'utilisation d'une porte OU). Par contre, le fil ''Grant'' relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 64i3iwt1xtrcv23b4f92narrpn1eajw 768532 768531 2026-06-24T18:59:20Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768532 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' partagé par tous les composants, et une série de fils ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Un composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Request'' est partagé entre tous les composants, qui sont tous connectés dessus. Quand plusieurs composants veulent mettre un 1 sur ce fils, le résultat sera un 1. En clair, le fil est un peu l'équivalent d'une porte OU entre les sorties ''Request'' de chaque composant. Le OU en question est ce qui s'appelle un ''OU cablé'', et on l'a abordé il y a quelques chapitres. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Il relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil Grant. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Quand un composant ne veut pas accéder au bus, il transmet le bit reçu sur ce fil tel quel, sans le modifier. Mais s'il veut accéder au bus, il mettra un zéro sur ce fil : les composants précédents verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> i50fd8kzp94g69iftkju8phkbpzhz2b 768533 768532 2026-06-24T19:05:20Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768533 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' partagé par tous les composants, et une série de fils ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Un composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Request'' est partagé entre tous les composants, qui sont tous connectés dessus. Quand plusieurs composants veulent mettre un 1 sur ce fils, le résultat sera un 1. En clair, le fil est un peu l'équivalent d'une porte OU entre les sorties ''Request'' de chaque composant. Le OU en question est ce qui s'appelle un ''OU cablé'', et on l'a abordé il y a quelques chapitres. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Il relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil ''Grant''. le signal ''Grant'' est donc transmis d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Un composant prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> rnoh3lum1cd434lohq6m23b28xj0vuc 768534 768533 2026-06-24T19:16:44Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768534 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Les composants sont reliés à l'arbitre via deux fils : un fil nommé ''Request'' partagé par tous les composants, et une série de fils ''Grant'' qui part de l'arbitre et parcours les composants un par un. Le fil ''Request'' transmet à l'arbitre une demande d'accès au bus. Un composant qui veut accéder au bus va placer un sur ce fil 1 quand il veut accéder au bus. Le fil ''Request'' est partagé entre tous les composants, qui sont tous connectés dessus. Quand plusieurs composants veulent mettre un 1 sur ce fils, le résultat sera un 1. En clair, le fil est un peu l'équivalent d'une porte OU entre les sorties ''Request'' de chaque composant. Le OU en question est ce qui s'appelle un ''OU cablé'', et on l'a abordé il y a quelques chapitres. Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Il relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil ''Grant''. le signal ''Grant'' est donc transmis d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Un composant prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> owwiuhqm1y37hjvctrdh8c36b9pop19 768535 768534 2026-06-24T19:29:00Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768535 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Les différents composants sont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une porte OU unique, soit avec une chaine de portes OU, soit avec un OU cablé. Avec cette dernière solution, il y a un fil ''Request'' partagé entre tous les composants, toutes les sorties ''Request'' sont ous connectées dessus. Quand plusieurs composants veulent mettre un 1 sur ce fils, le résultat sera un 1. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le fil ''Grant'' permet à l'arbitre de signaler qu'un des composants pourra avoir accès au bus. Il relie l'arbitre au premier composant, puis le premier composant au second, le second au troisième, etc. Tous les composants sont reliés en guirlande par ce fil ''Grant''. le signal ''Grant'' est donc transmis d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Par défaut, l'arbitre envoie un 1 quand il accepte un nouvel accès au bus (et un 0 quand il veut bloquer tout nouvel accès). Un composant prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 7d1thm8zngu3gxsfqges28qofi4avu7 768536 768535 2026-06-24T19:30:35Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768536 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Les différents composants sont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une porte OU unique, soit avec une chaine de portes OU, soit avec un OU cablé. Avec cette dernière solution, il y a un fil ''Request'' partagé entre tous les composants, toutes les sorties ''Request'' sont ous connectées dessus. Quand plusieurs composants veulent mettre un 1 sur ce fils, le résultat sera un 1. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> b2ym7j6helg2djrgh2lpkfmltp3bhyf 768537 768536 2026-06-24T19:32:13Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768537 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Les différents composants sont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une porte OU unique, soit avec une chaine de portes OU, soit avec un OU cablé. Avec cette dernière solution, il y a un fil ''Request'' partagé entre tous les composants, toutes les sorties ''Request'' sont ous connectées dessus. Quand plusieurs composants veulent mettre un 1 sur ce fils, le résultat sera un 1. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Busy'' est lui aussi généré de la même manière. Quand un composant reçoit l'accès au bus, il force ce signal à 1. Là aussi, il y a soit une chaine de portes OU, soit un ou câblé. Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 5bwi9igfurerr9y7n5wgo47mcbgjion 768538 768537 2026-06-24T19:33:22Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768538 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Même si le contrôleur de bus n'a qu'une seule entrée ''Request'', les composants ont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une porte OU unique, soit avec une chaine de portes OU, soit avec un OU cablé. Avec cette dernière solution, il y a un fil ''Request'' partagé entre tous les composants, toutes les sorties ''Request'' sont ous connectées dessus. Quand plusieurs composants veulent mettre un 1 sur ce fils, le résultat sera un 1. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Busy'' est lui aussi généré de la même manière. Quand un composant reçoit l'accès au bus, il force ce signal à 1. Là aussi, il y a soit une chaine de portes OU, soit un ou câblé. Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> hkfs31chxbj5i8jtsxebzuhsgs06t37 768539 768538 2026-06-24T19:36:31Z Mewtow 31375 /* L'arbitrage centralisé à daisy chain */ 768539 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Avec la première, les composants envoient leurs requêtes au contrôleur de bus sans se préoccuper de l'occupation du bus. Le contrôleur de bus sait si le bus est libre ou occupé et il décidera de mettre en attente toutes les requêtes tant que le bus est occupé. Avec la seconde, les composants savent si le bus est libre ou non. Tout composant peut réserver le bus si celui-ci est libre, mais attend si le bus est déjà réservé. Pour savoir si le bus est réservé, il existe deux méthodes : * soit chaque composant peut vérifier à tout moment si le bus est libre ou non (aucun composant n'écrit dessus) ; * soit on rajoute un bit qui indique si le bus est libre ou occupé : le bit busy. Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Même si le contrôleur de bus n'a qu'une seule entrée ''Request'', les composants ont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une chaine de portes OU, soit avec un OU cablé. Le signal ''Busy'' est lui aussi généré de la même manière. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> io8egd36zmnosma4c7jn9epfxsiiwq7 768540 768539 2026-06-24T19:45:54Z Mewtow 31375 /* L'arbitrage centralisé à requêtes indépendantes */ 768540 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Mais en général, un fil du bus de commande précise si le bus est libre. Il s'agit du bit '''''busy''''', qui indique si le bus est libre ou occupé. Quand un composant reçoit l'autorisation du contrôleur de bus, il met ce fil à 1, ce qui indique au contrôleur de bus que le bus est occupé. Il maintient ce fil à 1 tant qu'il utilise le bus, et le met à zéro quand il libère le bus. Le contrôleur de bus sait donc quand le bus est libre ou occupé, et décide de l'arbitrage en fonction. Pour cela, chaque composant a une sortie ''Busy'', qui indique qu'il utilise le bus. Le fil ''Busy'' est obtenu en faisant un OU entre les sorties ''Busy'' de chaque composant. Le OU en question est réalisé soit avec une chaine de portes OU, soit avec un OU câblé. La méthode d'arbitrage dépend alors du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Même si le contrôleur de bus n'a qu'une seule entrée ''Request'', les composants ont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une chaine de portes OU, soit avec un OU cablé. Le signal ''Busy'' est lui aussi généré de la même manière. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 1cd3ysu1akof8uyiw7ep8nm9hf9avbk 768541 768540 2026-06-24T19:47:27Z Mewtow 31375 /* L'arbitrage centralisé à requêtes indépendantes */ 768541 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ?Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Mais en général, un fil du bus de commande précise si le bus est libre. Il s'agit du bit '''''busy''''', qui indique si le bus est libre ou occupé. Quand un composant reçoit l'autorisation du contrôleur de bus, il met ce fil à 1, ce qui indique au contrôleur de bus que le bus est occupé. Il maintient ce fil à 1 tant qu'il utilise le bus, et le met à zéro quand il libère le bus. Le contrôleur de bus sait donc quand le bus est libre ou occupé, et décide de l'arbitrage en fonction. Pour cela, chaque composant a une sortie ''Busy'', qui indique qu'il utilise le bus. Le fil ''Busy'' est obtenu en faisant un OU entre les sorties ''Busy'' de chaque composant. Le OU en question est réalisé soit avec une chaine de portes OU, soit avec un OU câblé. [[File:Busy bit for bus arbitration.png|centre|vignette|upright=2|Busy bit for bus arbitration]] La méthode d'arbitrage dépend du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Même si le contrôleur de bus n'a qu'une seule entrée ''Request'', les composants ont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une chaine de portes OU, soit avec un OU cablé. Le signal ''Busy'' est lui aussi généré de la même manière. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> lv9nrhfvannhqmozkipihth3tky5bi5 768542 768541 2026-06-24T19:57:28Z Mewtow 31375 /* L'arbitrage centralisé à requêtes indépendantes */ 768542 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. [[File:CSMA-CD Verfahren.svg|centre|vignette|upright=2|Collisions lors de l'accès à un bus.]] Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. [[File:Arbitrage centralisé à requêtes indépendantes.png|centre|vignette|upright=2|Arbitrage centralisé à requêtes indépendantes.]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ? Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Mais en général, un fil du bus de commande précise si le bus est libre. Il s'agit du bit '''''Busy''''', qui indique si le bus est libre ou occupé. Quand un composant reçoit l'autorisation du contrôleur de bus, il met ce fil à 1, ce qui indique au contrôleur de bus que le bus est occupé. Il maintient ce fil à 1 tant qu'il utilise le bus, et le met à zéro quand il libère le bus. Le contrôleur de bus sait donc quand le bus est libre ou occupé, et décide de l'arbitrage en fonction. Pour cela, chaque composant a une sortie ''Busy'', qui indique qu'il utilise le bus. Le fil ''Busy'' est obtenu en faisant un OU entre les sorties ''Busy'' de chaque composant. Le OU en question est réalisé soit avec une chaine de portes OU, soit avec un OU câblé. [[File:Busy bit for bus arbitration.png|centre|vignette|upright=2|''Busy'' bit pour l'abitrage du bus.]] La méthode d'arbitrage dépend du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Même si le contrôleur de bus n'a qu'une seule entrée ''Request'', les composants ont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une chaine de portes OU, soit avec un OU cablé. Le signal ''Busy'' est lui aussi généré de la même manière. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> 8lfgxcjpvjxivk18lljlptqh65nd5h7 768543 768542 2026-06-24T20:01:42Z Mewtow 31375 /* L'arbitrage du bus */ 768543 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Il arrive que plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Ces conflits d'accès posent problème si un composant cherche à envoyer un 1 et l'autre un 0. Dans le pire des cas, tout ce que l’on reçoit à l'autre bout du fil est une espèce de mélange incohérent des deux. Les concepteurs de bus ont inventé des méthodes pour gérer ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. [[File:Arbitrage centralisé à requêtes indépendantes.png|centre|vignette|upright=2|Arbitrage centralisé à requêtes indépendantes.]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ? Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Mais en général, un fil du bus de commande précise si le bus est libre. Il s'agit du bit '''''Busy''''', qui indique si le bus est libre ou occupé. Quand un composant reçoit l'autorisation du contrôleur de bus, il met ce fil à 1, ce qui indique au contrôleur de bus que le bus est occupé. Il maintient ce fil à 1 tant qu'il utilise le bus, et le met à zéro quand il libère le bus. Le contrôleur de bus sait donc quand le bus est libre ou occupé, et décide de l'arbitrage en fonction. Pour cela, chaque composant a une sortie ''Busy'', qui indique qu'il utilise le bus. Le fil ''Busy'' est obtenu en faisant un OU entre les sorties ''Busy'' de chaque composant. Le OU en question est réalisé soit avec une chaine de portes OU, soit avec un OU câblé. [[File:Busy bit for bus arbitration.png|centre|vignette|upright=2|''Busy'' bit pour l'abitrage du bus.]] La méthode d'arbitrage dépend du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Même si le contrôleur de bus n'a qu'une seule entrée ''Request'', les composants ont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une chaine de portes OU, soit avec un OU cablé. Le signal ''Busy'' est lui aussi généré de la même manière. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> dd191pfsvm1dnqgbb7ge2montf3j5xb 768544 768543 2026-06-24T20:02:54Z Mewtow 31375 /* L'arbitrage du bus */ 768544 wikitext text/x-wiki Il y a quelques chapitres, nous avons vu la différence entre bus et liaison point à point : là où ces dernières ne connectent que deux composants, les bus de communication en connectent bien plus. Ce faisant, les bus de communication font face à de nouveaux problèmes, inconnus des liaisons point à point. Et ce sont ces problèmes qui font l'objet de ce chapitre. Autant le chapitre précédent valait à la fois pour les liaisons point à point et les bus, autant ce n'est pas le cas de celui-ci. Ce chapitre va parler de ce qui n'est valable que pour les bus de communication, comme leur arbitrage, la détection des collisions, etc. Tous ces problèmes ne peuvent pas survenir, par définition, sur les liaisons point à point. ==L'adressage du récepteur== [[File:Bus general schematic.svg|vignette|Schéma d'un bus.]] La trame doit naturellement être envoyée à un récepteur, seul destinataire de la trame. Sur les liaisons point à point, il n'y a pas besoin de préciser quel est le récepteur. Mais sur les bus, c'est une toute autre histoire. Tous les composants reliés aux bus sont de potentiels récepteurs et l'émetteur doit préciser à qui la trame est destinée. Pour résoudre ce problème, chaque composant se voit attribuer une '''adresse''', il est « numéroté ». Cela fonctionne aussi pour les composants qui sont des périphériques. ===L'adressage sur les bus parallèles et série=== Sur les bus parallèles, l'adresse est généralement transmise sur des fils à part, sur un sous-bus dédié appelé le bus d'adresse. En général, les adresses sur les bus pour périphériques sont assez petites, de quelques bits dans le cas le plus fréquent, quelques octets tout au plus. Il n'y a pas besoin de plus pour adresser une centaine de composants ou plus. Les seuls bus à avoir des adresses de plusieurs octets sont les bus liés aux mémoires, ou ceux qui ont un rapport avec les réseaux informatiques. Les '''bus multiplexés''' utilisent une astuce pour économiser des fils et des broches. Un bus multiplexé sert alternativement de bus de donnée ou d'adresse, suivant la valeur d'un bit du bus de commande. Ce dernier, le bit ''Adress Line Enable'' (ALE), précise si le contenu du bus est une adresse ou une donnée : il vaut 1 quand une adresse transite sur le bus, et 0 si le bus contient une donnée. Un défaut de ces bus est que les transferts sont plus lents, car l'adresse et la donnée ne sont pas envoyées en même temps lors d'une écriture. Un autre problème des bus multiplexé est qu'ils ont a peu près autant de bits pour coder l'adresse que pour transporter les données. Par exemple, un bus multiplexé de 8 bits transmettra des adresses de 16 bits, mais aussi des données de 16 bits. Ils sont donc moins versatiles, mais cela pose problème sur les bus où l'on peut connecter peu de périphériques. Dans ce cas, les adresses sont très petites et l'économie de fils est donc beaucoup plus faible. Passons maintenant aux '''bus série''' (ou certains bus parallèles particuliers). Pour arriver à destination, la trame doit indiquer l'adresse du composant de destination. Les récepteurs espionnent le bus en permanence pour détecter les trames qui leur sont destinées. Ils lisent toutes les trames envoyées sur le bus et en extraient l'adresse de destination : si celle-ci leur correspond, ils lisent le reste de la trame, ils ne la prennent pas en compte sinon. L'adresse en question est intégrée à la trame et est placée à un endroit précis, toujours le même, pour que le récepteur puisse l'extraire. Le plus souvent, l'adresse de destination est placée au début de la trame, afin qu'elle soit envoyée au plus vite. Ainsi, les périphériques savent plus rapidement si la trame leur est destinée ou non, l'adresse étant connue le plus tôt possible. ===Le décodage d'adresse=== Le fait d'attribuer une adresse à chaque composant est une idée simple, mais efficace. Encore faut-il la mettre en œuvre et il existe plusieurs possibilités pour cela. Implémenter l'adressage sur un bus demande à ce que chaque composant sache d'une manière ou d'une autre que c'est à lui que l'on veut parler et pas à un autre. Lorsqu'une adresse est envoyée sur le bus, seul l'émetteur et le récepteur se connectent au bus, les autres composants ne sont pas censés réagir. Et pour cela, il existe deux possibilités : soit on délègue l'adressage au composant, soit on ajoute un circuit qui active le composant adressé et désactive les autres. Avec la première méthode, les composants branchés sur le bus monitorent en permanence ce qui est transféré sur le bus. Quand un envoi de commande a lieu, chaque composant extrait l'adresse transmise sur le bus et vérifie si c'est bien la sienne. Si c'est le cas, le composant se connecte sur le bus et les autres composants se déconnectent. En conséquence, chaque composant contient un comparateur pour cette vérification d'adresse, dont la sortie commande les circuits trois états qui relient le contrôleur au bus. Cette méthode est particulièrement pratique sur les bus où le bus d'adresse est séparé du bus de données. Si ce n'est pas le cas, le composant doit mémoriser l'adresse transmise sur le bus dans un registre, avant de faire la comparaison? Même chose sur les bus série. La seconde solution est celle du '''décodage d'adresse'''. Elle utilise un circuit qui détermine, à partir de l'adresse, quel est le composant adressé. Seul ce composant sera activé/connecté au bus, tandis que les autres seront désactivés/déconnectés du bus. Pour implémenter la dernière solution, chaque périphérique possède une entrée CS, qui active ou désactive le composant suivant sa valeur. Le composant se déconnecte du bus si ce bit est à 0 et est connecté s'il est à 1. Pour éviter les conflits, un seul composant doit avoir son bit CS à 1. Pour cela, il faut ajouter un circuit qui prend en entrée l'adresse et qui commande les bits CS : ce circuit est un circuit de décodage partiel d'adresse. [[File:Décodage d'adresse sur un bus.png|centre|vignette|upright=2|Décodage d'adresse sur un bus]] ==L'interfaçage avec le bus== Une fois que l'on sait quel composant a accès au bus à un instant donné, il faut trouver un moyen pour que les composants non sélectionnés par l'arbitrage ne puissent pas écrire sur le bus. Une première solution consiste à relier les entrées/sorties des composants au bus via un multiplexeur/démultiplexeur : on est alors certain que seul un composant pourra émettre sur le bus à un moment donné. L'arbitrage du bus choisit quel composant peut émettre, et configure l'entrée de commande du multiplexeur en fonction. Les multiplexeurs et démultiplexeurs sont configurés en utilisant l'adresse du composant émetteur/récepteur. Une autre solution consiste à connecter et déconnecter les circuits du bus selon les besoins. À un instant t, seul l'émetteur et le récepteur sont connectés au bus. Mais cela demande pouvoir déconnecter du bus les entrées/sorties qui n'envoient pas de données. Plus précisément, leurs sorties peuvent être mises dans un état de haute impédance, qui n'est ni un 0 ni un 1. Quand une sortie est en haute impédance, elle n'a pas la moindre influence sur le bus et ne peut donc pas y écrire. Tout se passe comme si elle était déconnectée du bus, et dans les faits, elle l'est souvent. Dans le chapitre sur les circuits intégrés, nous avons vu qu'il existait trois types de sorties : les sorties totem-pole, à drain/collecteur ouvert, et trois-état. Les sorties totem-pole fournissent soit un 1, soit un zéro, et ne peuvent pas être déconnectées proprement dit. Les deux autres types de sorties en sont capables. Et nous allons les voir dans ce qui suit. ===L'interfaçage avec le bus avec des circuits trois-états=== Le cas le plus simple est celui des sorties trois-état, qui peuvent soit fournir un 1, soit fournir un 0, soit être déconnectées. Malheureusement, les circuits intégrés normaux n'ont pas naturellement des entrées-sorties trois-état. Les portes logiques fournissent soit un 0, soit un 1, pas d'état déconnecté. [[File:Tampons3E.png|vignette|Tampons 3 états.]] La solution retenue sur presque tous les circuits actuels est d'utiliser des '''tampons trois états'''. Pour rappel, nous avions vu ce circuit dans le chapitre sur les circuits intégrés, mais un rappel ne fera clairement pas de mal. Un tampon trois-états peut être vu comme une porte OUI modifiée, qui peut déconnecter sa sortie de son entrée. Un tampon trois-état possède une entrée de donnée, une entrée de commande, et une sortie : suivant ce qui est mis sur l'entrée de commande, la sortie est soit en état de haute impédance (déconnectée du bus), soit égale à l'entrée. {|class="wikitable" |- !Commande!!Entrée!!Sortie |- ||0||0||Haute impédance/Déconnexion |- ||0||1||Haute impédance/Déconnexion |- ||1||0||0 |- ||1||1||1 |} [[File:Tristate buffer.svg|centre|vignette|upright=1.5|Tampon trois-états.]] On peut utiliser ces tampons trois états pour permettre à un composant d'émettre ou de recevoir des données sur un bus. Par exemple, on peut utiliser ces tampons pour autoriser les émissions sur le bus, le composant étant déconnecté (haute impédance) s'il n'a rien à émettre. Le composant a accès au bus en écriture seule. L'exemple typique est celui d'une mémoire ROM reliée à un bus de données. [[File:Bus en écriture seule.png|centre|vignette|upright=1.5|Bus en écriture seule.]] Une autre possibilité est de permettre à un composant de recevoir des données sur le bus. Le composant peut alors surveiller le bus et regarder si des données lui sont transmises, ou se déconnecter du bus. Le composant a alors accès au bus en lecture seule. [[File:Bus en lecture seule.png|centre|vignette|upright=1.5|Bus en lecture seule.]] Évidemment, on peut autoriser lectures et écritures : le composant peut alors aussi bien émettre que recevoir des données sur le bus quand il s'y connecte. On doit alors utiliser deux circuits trois états, un pour l'émission/écriture et un autre pour la réception/lecture. Comme exemple, on pourrait citer les mémoires RAM, qui sont reliées au bus mémoire par des circuits de ce genre. Dans ce cas, les circuits trois états doivent être commandés par le bit CS (''Chip Select'') qui connecte ou déconnecte la mémoire du bus, mais aussi par le bit R/W (''Read/Write'') qui décide du sens de transfert. Pour faire la traduction entre ces deux bits et les bits à placer sur l'entrée de commande des circuits trois états, on utilise un petit circuit combinatoire assez simple. [[File:Bus en lecture et écriture.png|centre|vignette|upright=2|Bus en lecture et écriture.]] ===L'interfaçage avec le bus avec des circuits à drain/collecteur ouvert=== Les sorties à drain/collecteur ouvert sont plus limitées et ne peuvent prendre que deux états. Dans le cas le plus fréquent, la sortie est soit déconnectée, soit mise à 0 par le circuit intégré, mais elle ne peut pas être mise à 1 sans intervention extérieure. Pour compenser cela, le bus est relié à la tension d'alimentation à travers une résistance, appelée résistance de rappel. Cela garantit que le bus est naturellement à l'état 1, du moins tant que les sorties des composants sont déconnectées. Au repos, quand les composants n’envoient rien sur le bus, les sorties des composants sont déconnectées et les résistances de rappel mettent le bus à 1. Mais quand un seul composant met sa sortie à 0, cela force le bus à passer à 0. [[File:Open-Collector-Prinzip.JPG|centre|vignette|upright=2|Exemple de bus n'utilisant que des composants à sortie en collecteur ouvert.]] Pour le dire autrement, on peut voir le contenu du bus comme un ET des bits envoyés sur les sorties des composants connectés au bus. Ce détail aura son importance par la suite. Le contenu du fil peut être lu sans altérer l'état électrique du bus/fil. Avec cette méthode, le nombre de composants que l'on peut placer sur le bus est surtout limité par les spécifications électriques du bus, notamment sa capacité. Mais cela a l'avantage que le bus est compatible avec des technologies de fabrication totalement différentes, qu'il s'agisse de composants TTL, CMOS ou autres. En effet, la tension d'alimentation des composants TTL n'est pas la même que celle des composants CMOS. Utiliser des entrées-sorties à drain ouvert fait que l'on peut choisir la tension d'alimentation que l'on veut, et donc que l'on peut choisir entre TTL et CMOS. Par contre, on ne peut pas connecter composants TTL et CMOS avec des tensions d'alimentation différentes sur un même bus. Il est possible de mélanger sorties à drain/collecteur ouvert, avec des entrées "trois-états" (des entrées qui peuvent soit permettre une lecture du bus, soit être déconnectées). C'est par exemple le cas sur les microprocesseurs 8051. [[File:Port8051.png|centre|vignette|upright=2|Port d'un 8051]] ==L'arbitrage du bus== Si plusieurs composants tentent d'envoyer une donnée sur le bus en même temps : c'est un '''conflit d'accès au bus'''. Les conflits d'accès au bus surviennent sur la majeure partie des bus, qu'ils soient multiplexés ou non. Sur les bus multiplexés, qui relient plus de deux composants, cette situation est fréquente du fait du nombre de récepteurs/émetteurs potentiels. Mais cela peut aussi arriver sur certains bus dédiés, les bus ''half-duplex'' étant des exemples particuliers : il se peut que les deux composants veuillent être émetteurs en même temps, ou récepteurs en même temps. Et de tels conflits sont censés être évités d'une manière ou d'une autre, vu qu'un bus ne permet pas plusieurs transferts simultanés dans le même sens. Les concepteurs de bus ont inventé des méthodes pour éviter ces conflits d’accès, et choisir le plus efficacement possible l’émetteur : on parle d''''arbitrage du bus'''. L'arbitrage du bus implique qu'il faut répartir l'accès au bus pour n'avoir qu'un émetteur à la fois. On doit choisir un émetteur parmi plusieurs candidats à l'émission. Ce choix sera effectué différemment suivant le protocole du bus et son organisation, mais ce choix n’est pas gratuit. Un seul candidat sera choisit, et les autres devront attendre leur tour pour avoir accès au bus. ===Les différents types d'arbitrage de bus=== Il existe plusieurs méthodes d'arbitrages, qui peuvent se classer en différents types, selon leur fonctionnement. La première distingue l'arbitrage centralisé de l'arbitrage décentralisé. La seconde distingue l'arbitrage à priorité et à partage égal. Les deux catégories interagissent l'une avec l'autre, comme on le verra dans ce qui suit. Une première classification distingue l'arbitrage à part égal de l'arbitrage à priorité. La différence est que le second donne la priorité à certains composants sur les autres, alors que l'arbitrage à part égales ne donne aucun traitement de faveur. L''''arbitrage à part égales''' peut se résumer en une phrase : chacun son tour ! Chaque composant a accès au bus à tour de rôle. Cette méthode fort simple convient si les différents composants ont des besoins approximativement équilibrés. Mais elle n'est pas adaptée quand certains composants effectuent beaucoup de transactions que les autres. Les composants gourmands manqueront de débit, alors que les autres monopoliseront le bus pour ne presque rien en faire. A l'opposé, l''''arbitrage à priorité''' donne la priorité à certains composants sur les autres. Tous les composants se voient attribuer une priorité, un nombre qui indique s'ils sont prioritaire sur les autres. Plus ce nombre est élevé, plus la priorité est importante (ou inversement). Lorsque plusieurs composants veulent accéder au bus, celui qui a la priorité la plus haute passe devant tous les autres. Certaines méthodes d'arbitrage permettent de libérer le bus de force pour laisser la place à un autre composant. On parle alors de '''''bus mastering'''''. L'idée est de libèrer le bus de force si un composant plus prioritaire veut utiliser le bus. Les méthodes d'arbitrage à priorité sont nombreuses, alors qu'il n'y a pas 36 façons d'implémenter l'arbitrage à parts égales. Mais les deux ne sont pas utilisés dans les mêmes circonstances. Par exemple, imaginons un bus qui relie plusieurs processeurs/cœurs identiques à la mémoire RAM. Un tel arrangement est appelé un bus mémoire partagé (sous-entendu, entre plusieurs processeurs/cœurs). L'arbitrage n'a aucun raison de prioriser un processeur sur un autre, vu qu'ils sont identiques. En conséquence, l'arbitrage à parts égales est parfaitement adapté. Pour un bus système connecté à un processeur rapide, une mémoire et des entrées-sorties lentes, il vaut mieux privilégier le processeur sur les entrées-sorties. Un arbitrage à priorité est alors utilisé. Une seconde classification nous dit si un composant gère le bus, ou si cet arbitrage est délégué aux composants qui accèdent au bus. * Dans l''''arbitrage centralisé''', un circuit spécialisé s'occupe de l'arbitrage du bus. * Dans l''''arbitrage distribué''', chaque composant se débrouille de concert avec tous les autres pour éviter les conflits d’accès au bus : chaque composant décide seul d'émettre ou pas, suivant l'état du bus. : ''Notons qu'un même algorithme peut être implémenté soit de manière centralisée, soit de manière décentralisée.'' Avec l'arbitrage centralisé, l'arbitrage du bus est géré par un '''contrôleur de bus'''. Il s'agit d'un circuit qui prend en charge tout ce qui a trait au bus, au minimum l'arbitrage du bus, souvent d'autres fonctions en plus. Il communique avec les composants branchés sur le bus, dans les deux sens. Il reçoit des signaux en provenance des composants connectés au bus, et leur envoie des signaux de commande. Il est typiquement soudé à la carte mère et est surtout utilisé avec des bus système ou des bus présents sur la carte mère. Sur les premiers PC, le contrôleurs de bus et le circuit d’arbitrage étaient séparés. Il étaient dans deux circuits imprimés différents, tous deux soudés sur la carte mère. Le contrôleur de bus était un Intel 8288, alors que le circuit d'arbitrage était un 8289 d'Intel. Les deux étaient conçus pour fonctionner en tandem, mais ils étaient séparés pour des raisons de budget en transistor. De nos jours, les deux composants sont fusionnés dans le ''chipset'' de la carte mère. L''''arbitrage décentralisé''' est très rarement utilisé. Les exemples les plus connus sont les bus CAN et I²c, qui sont certes très utilisés. Mais la majorité des bus n'utilisent pas cet arbitrage décentralisé. La plupart des bus à arbitrage décentralisé utilisent des circuits imprimés spécifiques, qui ont des sorties à collecteur ouvert. L'arbitrage décentralisé se base sur les propriétés électriques de ce collecteur ouvert, comme on le verra plus bas. Il s'accompagne toujours d'un système de priorité. {|class="wikitable" |- ! !! Arbitrage centralisé !! Arbitrage décentralisé |- ! Arbitrage à priorité | Possible, courant. || Systématique. |- ! Arbitrage à parts égales | Possible, peu courant. || Impossible. |} Dans ce qui va suivre, nous allons voir diverses méthodes d'arbitrage de bus. Nous allons surtout voir des méthodes centralisées, car elles sont nettement plus simples à comprendre. Sauf exception, elles fonctionnent toutes sur le même principe. Les composants envoient une requête au contrôleur de bus, pour demander l'accès au bus. Le contrôleur de bus analyse les requêtes et décide quel composant a l'accès exclusif au bus, à un instant donné. Le composant en question est appelé le '''''bus master''''', ou maitre du bus. Pour résumer, le contrôleur de bus élit le ''bus master'' parmi une liste de candidats, qui ont demandé une requête d'accès au bus. La sélection du ''bus master'' varie d'un bus à l'autre. Certains bus donnent l'accès à chaque candidat à part égale, d'autres ont un système de priorité où certains composants passent avant les autres. ===L'arbitrage centralisé à requêtes indépendantes=== L''''arbitrage par requêtes indépendantes''' est le plus simple à comprendre, car il applique ce principe de requête et de sélection à la lettre. Chaque composant connecté au bus est connecté au contrôleur de bus, avec une liaison point à point. Chaque composant peut donc candidater pour accéder au bus indépendamment des autres. Et le contrôleur de bus lui communique sa réponse par sa propre liaison point à point. Dans le détail, les composants sont reliés au contrôleur de bus avec deux fils, chacun transmettant un bit : un fil ''Request'' et un fil ''Grant''. Le bit ''Request'' permet au composant d'envoyer une requête d'accès au bus : il est mis à 1 quand le composant veut accéder au bus. Il est donc connecté à une sortie du composant, mais est une entrée pour le contrôleur de bus. Le bit ''Grant'' est généré par le contrôleur de bus et est envoyé au composant élu, celui auquel il donne l'accès au bus. S'il est à 1, le composant qui reçoit ce signal se connecte au bus, les autres attendent leur tour. [[File:Arbitrage centralisé à requêtes indépendantes.png|centre|vignette|upright=2|Arbitrage centralisé à requêtes indépendantes.]] Vous avez sans doute pensé à un détail : que ce se passe-t-il quand le bus est occupé ? Est-ce que les composants sont au courant que le bus est occupé ou non ? La réponse dépend du bus et de la méthode d'arbitrage, mais il y a deux grandes réponses principales. Mais en général, un fil du bus de commande précise si le bus est libre. Il s'agit du bit '''''Busy''''', qui indique si le bus est libre ou occupé. Quand un composant reçoit l'autorisation du contrôleur de bus, il met ce fil à 1, ce qui indique au contrôleur de bus que le bus est occupé. Il maintient ce fil à 1 tant qu'il utilise le bus, et le met à zéro quand il libère le bus. Le contrôleur de bus sait donc quand le bus est libre ou occupé, et décide de l'arbitrage en fonction. Pour cela, chaque composant a une sortie ''Busy'', qui indique qu'il utilise le bus. Le fil ''Busy'' est obtenu en faisant un OU entre les sorties ''Busy'' de chaque composant. Le OU en question est réalisé soit avec une chaine de portes OU, soit avec un OU câblé. [[File:Busy bit for bus arbitration.png|centre|vignette|upright=2|''Busy'' bit pour l'abitrage du bus.]] La méthode d'arbitrage dépend du contrôleur de bus utilisé. Il est possible d'utiliser un microcontroleur comme contrôleur de bus, ce qui permet de programmer l’algorithme d'arbitrage. Mais il est aussi possible d'utiliser un circuit dédié. L'arbitrage par requêtes indépendante peut implémenter l'arbitrage à part égale, comme un arbitrage à priorité. L'arbitrage à part égale est implémenté avec un simple compteur couplé à un décodeur. Le compteur indique quel composant a accès au bus, le décodeur traduit ce nombre en signaux ''Grant''. Le compteur est incrémenté lorsqu'un composant libère le bus. Pour l'arbitrage à priorité, il est possible de l'implémenter avec un encodeur à priorité, dont les entrées sont connectées au signaux ''Request''. Le résultat de l'encodeur à priorité passe dans un décodeur, ce qui donne les signaux ''Grant'' adéquats. Bien sur, il y a d'autres circuits autour, qui font en sorte que l'envoi des signaux ''Grant'' se fasse seulement quand le bus est libéré, et bien d'autres choses. [[File:Arbitrage de bus centralisé, à résolution parallèle.png|centre|vignette|upright=2|Arbitrage de bus centralisé, à résolution parallèle]] Un défaut de cette solution est qu'elle demande de câbler beaucoup de fils. Aussi, les autres méthodes procèdent autrement, afin d'économiser des fils. Les deux méthodes que nous allons voir mutualisent certains fils entre plusieurs composants. Par exemple, avec la méthode du ''pooling'', il n'y a qu'un seul fil ''Request'' partagé entre tous les composants. Et avec la ''Daisy chain'', cela va plus loin car tous les fils ''Grant'' sont mutualisés. ===L'arbitrage centralisé par adressage=== Dans la section précédente, le contrôleur de bus doit utiliser N sorties ''Grant'' pour sélectionner le composant adéquat. En contrepartie, chaque composant n'a qu'une seule entrée ''Grant''. Une solution intermédiaire réduit le nombre de signaux ''Grant'', au prix d'une augmentation des entrées sur chaque composant. Mais au total, le nombre de fils est grandement réduit. L'idée est de remplacer les signaux ''Grant'' par l'adresse du composant sélectionné. Avec ce système, tous les composants ont une adresse qui permet de les sélectionner. Il est possible de configurer cette adresse pour chaque composant, à savoir que chaque composant mémorise son adresse dans un registre. Il y a un bus d'adresse séparé pour l'arbitrage, qui est commandé par le contrôleur de bus. Le contrôleur de bus envoie l'adresse du ''bus master'' sur ce bus. Les composants lisent cette adresse et détectent s'il s'agit de la leur. Si l'adresse ne lui est pas destiné, le composant sait qu'il n'y a pas accès au bus. Mais si c'est le cas, le composant sait qu'il est sélectionné et se connecte au bus. Au final, au lieu d'avoir N fils ''Grant'' pour N composants, le bus d'adresse fait beaucoup moins. Par exemple, pour 16 composants, on passe de 16 fils à 4. Le contrôleur de bus économise donc beaucoup de broches. Mais cela se fait en ajoutant des broches sur les composants, sans compter que ceux-ci doivent mémoriser leur adresse dans un registre. Mais rien d'insurmontable, le cout en circuits est très faible. : Notons qu'un tel arbitrage se marie assez bien avec le ''Direct Memory Access''. ===L'arbitrage centralisé à ''daisy chain''=== L''''arbitrage par ''daisy chain''''' est un algorithme centralisé, dans lequel tout composant a une priorité fixe. Dans celui-ci, tous les composants sont reliés à un arbitre, qui dit si l'accès au bus est autorisé. Cependant, le système utilise encore moins de fils que les arbitrages précédents. L'arbitre du bus n'a qu'une seule entrée ''Request'', et une sortie ''Grant''. Il y a aussi un fil ''Busy'' qui indique si le bus est libre ou non. Même si le contrôleur de bus n'a qu'une seule entrée ''Request'', les composants ont tous une sortie ''Request''. L'idée est que l'entrée ''Request'' du contrôleur de bus est un OU entre toutes les sorties ''Request''. Le contrôleur de bus sait donc qu'un composant a demandé l'accès au bus, mais il ne sait pas lequel. Et la ''Daisy Chain'' fait qu'il n'a pas à le savoir. Le OU entre les sorties ''Request'' peut se faire de plusieurs manières. Soit avec une chaine de portes OU, soit avec un OU cablé. Le signal ''Busy'' est lui aussi généré de la même manière. [[File:Implémentation du OU du fil REQUEST dans une Daisy chain.png|centre|vignette|upright=2|Implémentation du OU du fil REQUEST dans une Daisy chain.]] Le signal ''Grant'' est propagé d'un composant à l'autre, jusqu'à atteindre le premier composant à avoir demandé d'accès au bus. Pour cela, les composants sont reliés en guirlande, à savoir que chacun prend en entrée un signal ''Grant'' et fournit un second signal ''Grant'' sur une sortie du même nom. Si le composant n'a pas demandé l'accès au bus, il recopie le signal ''Grant'' en entrée sur sa sortie, sans le modifier. Mais s'il veut accéder au bus, il force sa sortie ''Grant'' à zéro : les composants suivants verront ainsi un 1 sur le fil, mais les suivants verront un zéro (interdiction d'accès). Ainsi, les composants les plus près du bus, dans l'ordre de la guirlande, seront prioritaires sur les autres. [[File:Decentralizz.jpg|centre|vignette|upright=2|Daisy Chain.]] Un composant utilise le signal ''Grant'' en entrée pour deux choses : savoir s'il a accès au bus, calculer le signal ''Grant'' en sortie. Pour ces deux opérations, le composant combine le signal ''Grant'' d'entrée avec le signal ''Request''. Le composant a accès au bus quand : il a demandé l'accès au bus, le signal ''Grant'' lui accorde l'accès. La première condition correspond à un signal ''Request'' à 1, la seconde à un signal ''Grant'' à 1. Une vulgaire porte ET fait l'affaire. Pour générer le signal ''Grant'' en sortie, il faut établir sa table de vérité, et il s’avère qu'il s'agit d'une porte XOR entre les deux signaux. [[File:Implémentation de la Daisy chain dans les émeteurs et récepteurs.png|centre|vignette|upright=2|Implémentation de la Daisy chain dans les émeteurs et récepteurs]] ===L'arbitrage décentralisé sur les bus à collecteur ouvert=== Les bus à collecteur ouvert ont un avantage pour ce qui est de l'arbitrage : ils permettent de détecter les collisions assez simplement. En effet, le contenu du bus est égal à un ET entre toutes les sorties reliées au bus. Si tous les composants veulent laisser le bus à 1 à un instant t, le bus sera à 1 : s'il y a collision, elle n'est pas grave car tous les composants envoient la même chose. Pareil s'ils veulent tous mettre le bus à 0 : le bus sera à 0 et la collision n'aura aucun impact. Par contre, si une sortie veut mettre le bus à 0 et un autre veut le laisser à 1, alors le bus sera mis à 0. La détection des collisions est alors évidente. Les composants qui émettent quelque chose sur le bus vérifient si le bus a bien la valeur qu'ils envoient dessus. Si les deux concordent, on ne sait pas il y a collision et il y a de bonnes chances que ce ne soit pas le cas, alors on continue la transmission. Mais si un composant envoie un 1 et que le bus est à 0, cela signifie qu'un autre composant a mis le bus à 0 et qu'il y a une collision. Le composant qui a détecté la collision cesse immédiatement la transmission et laisse la place au composant qui a mis le bus à 0, il le laisse finir la transmission entamée. Un exemple de ce genre est celui du bus CAN, très utilisé dans le domaine automobile. Lorsque le bus est libéré, les composants qui veulent accéder au bus envoient leur adresse sur le bus. La trame CAN est conçue de manière à ce que l'adresse de l'émetteur est transmise en premier. Le bus étant à collecteur ouvert, plusieurs composants pourront envoyer leur adresse sur ce bus en même temps. Tant que les bits d'adresse transmis sont identiques, il n'y a pas de collision. Par contre, le premier composant à forcer le bus à zéro gagnera la compétition et continuera à transmettre son adresse. Les autres auront perdu d'arbitrage. La conséquence est que l'arbitrage utilise l'adresse pour déterminer la priorité. Plus l'adresse d'un composant sera petite, plus il sera prioritaire sur les autres. [[File:Exemple d'arbitrage CAN.png|centre|vignette|upright=2|Exemple d'arbitrage CAN]] <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=Les liaisons point à point | prevText=Les liaisons point à point | next=Quelques exemples de bus et de liaisons point à point | nextText=Quelques exemples de bus et de liaisons point à point }}{{autocat}} </noinclude> hqsqome25k4wxjc0tjd2sg7q4sbs42l Python pour le calcul scientifique/Découverte de Python et de Jupyter 0 72864 768558 767659 2026-06-25T07:05:49Z Cdang 1202 /* Les booléens */ 768558 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruciton <code>divmod()</code> est un appelable. == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] qwv9p5pa8q4c5n2lewg8uciec8stg44 768560 768558 2026-06-25T07:09:28Z Cdang 1202 /* Les booléens */ cf. 768560 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruciton <code>divmod()</code> est un appelable. == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] 6hdfrwioggx6ivl61nuwfmbvj2wl06o 768564 768560 2026-06-25T07:50:37Z Cdang 1202 /* Appelable ({{lang|en|callable}}) */ itérable 768564 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruciton <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</cor> (voir ''[[../../Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"/code> ou <code>'…'/code> ; * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") # 1 2 3 A B C 1 2 3 </syntaxhighlight> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] lo5z7ytensmtsb95sj399hxcnvbhnwr 768565 768564 2026-06-25T07:54:22Z Cdang 1202 /* Itérable */ dico 768565 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruciton <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"/code> ou <code>'…'</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] ijr16xbuswbbvich0infin0u9gal6si 768566 768565 2026-06-25T07:55:52Z Cdang 1202 /* Itérable */ typo 768566 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruciton <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] ir93ursuklbuytgar19qvus9h3frm5t 768568 768566 2026-06-25T08:10:32Z Cdang 1202 /* Appelable ({{lang|en|callable}}) */ typo 768568 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] roy0b3mzqa4ehuuw5pfjfkj2p2vjusu 768570 768568 2026-06-25T08:24:59Z Cdang 1202 /* Itérable */ tranchage 768570 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dicitonnaires. == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] fa1bde81bfuyx5y6oboe9bhnow0h7bo 768571 768570 2026-06-25T08:29:14Z Cdang 1202 /* Itérable */ compress 768571 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dicitonnaires. On peut utiliser une liste de booléens pour extraire une partie d'un itérable, avec la fonction <code>compress()</code> du module <code>itertools</code>. <syntaxhighlight lang="Python"> from itertools import compress A = [1, 2, 3, 4, 5] booleen = [True, False, True, False, True] print(list(compress(A, booleen))) # [1, 3, 5] </syntaxhighlight> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] 3jtqairwq25ump52p16tbeb9nc4qyug 768572 768571 2026-06-25T08:32:26Z Cdang 1202 /* Itérable */ 768572 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dictionnaires. On peut utiliser une liste de booléens pour extraire une partie d'un itérable, avec la fonction <code>compress()</code> du module <code>itertools</code>. <syntaxhighlight lang="Python"> from itertools import compress A = [1, 2, 3, 4, 5] booleen = [True, False, True, False, True] print(list(compress(A, booleen))) # [1, 3, 5] </syntaxhighlight> Cette fonction est beaucoup plus performante qu'une extraction itérative de type <code>[a for i, a in enumerate(A) if booleen[i]]</code> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] nuteqma4pempramzqxu86pybs8zqy3g 768573 768572 2026-06-25T08:36:59Z Cdang 1202 /* Itérable */ enumerate 768573 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dictionnaires. La commande <code>enumerate()</code> renvoie la liste des éléments de l'itérable en l'associant à son indice : <syntaxhighlight lang="Python"> A = ["a", "b", "c"] print(list(enumerate(A))) # [(0, 'a'), (1, 'b'), (2, 'c')] </syntaxhighlight> On peut utiliser une liste de booléens pour extraire une partie d'un itérable, avec la fonction <code>compress()</code> du module <code>itertools</code>. <syntaxhighlight lang="Python"> from itertools import compress A = [1, 2, 3, 4, 5] booleen = [True, False, True, False, True] print(list(compress(A, booleen))) # [1, 3, 5] </syntaxhighlight> Cette fonction est beaucoup plus performante qu'une extraction itérative de type <code>[a for i, a in enumerate(A) if booleen[i]]</code> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] ju4chbqxdg705ykzi53cuxds5zhd3vm 768574 768573 2026-06-25T08:40:39Z Cdang 1202 /* Itérable */ zip() 768574 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dictionnaires. La commande <code>enumerate()</code> renvoie la liste des éléments de l'itérable en l'associant à son indice : <syntaxhighlight lang="Python"> A = ["a", "b", "c"] print(list(enumerate(A))) # [(0, 'a'), (1, 'b'), (2, 'c')] </syntaxhighlight> On peut utiliser un paramètre <code>start ''i''</code> pour commencer l'énumération à l'élément ''i'' + 1. La commande <code>zip()</code> permet d'itérer sur deux itérables en parallèle. <syntaxhighlight lang="Python"> A = ["a", "b", "c"] B = [6, 7, 8] for i in zip(A, B) # ('a', 6) # ('b', 7) # ('c', 8) </syntaxhighlight> On peut utiliser une liste de booléens pour extraire une partie d'un itérable, avec la fonction <code>compress()</code> du module <code>itertools</code>. <syntaxhighlight lang="Python"> from itertools import compress A = [1, 2, 3, 4, 5] booleen = [True, False, True, False, True] print(list(compress(A, booleen))) # [1, 3, 5] </syntaxhighlight> Cette fonction est beaucoup plus performante qu'une extraction itérative de type <code>[a for i, a in enumerate(A) if booleen[i]]</code> == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] leuaqdcan6adds530bxlftb8coku8az 768576 768574 2026-06-25T08:44:12Z Cdang 1202 /* Itérable */ alternative 768576 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dictionnaires. La commande <code>enumerate()</code> renvoie la liste des éléments de l'itérable en l'associant à son indice : <syntaxhighlight lang="Python"> A = ["a", "b", "c"] print(list(enumerate(A))) # [(0, 'a'), (1, 'b'), (2, 'c')] </syntaxhighlight> On peut utiliser un paramètre <code>start ''i''</code> pour commencer l'énumération à l'élément ''i'' + 1. La commande <code>zip()</code> permet d'itérer sur deux itérables en parallèle. <syntaxhighlight lang="Python"> A = ["a", "b", "c"] B = [6, 7, 8] for i in zip(A, B) # ('a', 6) # ('b', 7) # ('c', 8) </syntaxhighlight> On peut utiliser une liste de booléens pour extraire une partie d'un itérable, avec la fonction <code>compress()</code> du module <code>itertools</code>. <syntaxhighlight lang="Python"> from itertools import compress A = [1, 2, 3, 4, 5] booleen = [True, False, True, False, True] print(list(compress(A, booleen))) # [1, 3, 5] </syntaxhighlight> Cette fonction est beaucoup plus performante qu'une extraction itérative de type <code>[a for i, a in enumerate(A) if booleen[i]]</code> ou <code>[a for (a, b) in zip(A, booleen) if b]</code>. == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] qdmjzfw0erkctk0eix50ogu1vdw5uyc 768577 768576 2026-06-25T08:48:08Z Cdang 1202 /* Itérable */ exemple 768577 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dictionnaires. La commande <code>enumerate()</code> renvoie la liste des éléments de l'itérable en l'associant à son indice : <syntaxhighlight lang="Python"> A = ["a", "b", "c"] print(list(enumerate(A))) # [(0, 'a'), (1, 'b'), (2, 'c')] print([a for i, a in enumerate(A) if i % 2 == 0]) # filtre les indices pairs # ['a', 'c'] </syntaxhighlight> On peut utiliser un paramètre <code>start ''i''</code> pour commencer l'énumération à l'élément ''i'' + 1. La commande <code>zip()</code> permet d'itérer sur deux itérables en parallèle. <syntaxhighlight lang="Python"> A = ["a", "b", "c"] B = [6, 7, 8] for i in zip(A, B) # ('a', 6) # ('b', 7) # ('c', 8) </syntaxhighlight> On peut utiliser une liste de booléens pour extraire une partie d'un itérable, avec la fonction <code>compress()</code> du module <code>itertools</code>. <syntaxhighlight lang="Python"> from itertools import compress A = [1, 2, 3, 4, 5] booleen = [True, False, True, False, True] print(list(compress(A, booleen))) # [1, 3, 5] </syntaxhighlight> Cette fonction est beaucoup plus performante qu'une extraction itérative de type <code>[a for i, a in enumerate(A) if booleen[i]]</code> ou <code>[a for (a, b) in zip(A, booleen) if b]</code>. == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] fm4a235zka26qmkzre20e5ryshx5soo 768578 768577 2026-06-25T08:51:05Z Cdang 1202 /* Itérable */ lien 768578 wikitext text/x-wiki Python est un langage interprété. Il peut s'utiliser en ligne de commande, l'invite étant représentée par trois chevrons <syntaxhighlight lang="python"> >>> 3+2 5 </syntaxhighlight> Le code source peut être mis dans un fichier texte portant l'extension de nom de fichier <code>.py</code>. Cela requiert donc un éditeur de texte. Les fichiers que nous allons créer commenceront tous de la manière suivante : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> La première ligne, appelée ''[[wiktionary:fr:shebang|shebang]]'', n'est pas utile avec l'environnement que nous allons utiliser mais il est intéressant de la mettre pour assurer une compatibilité du code avec d'autres environnements. Les lignes commençant par <code lang="python">import</code> permettent d'importer des bibliothèques de fonctions ; nous en ajouterons au gré des besoins. Nous allons utiliser l'environnement de programmation Jupyter, un environnement libre, gratuit et multiplateforme. Jupyter utilise un navigateur Internet comme éditeur de texte et pour l'affichage des résultats. == Installer Jupyter == [[Fichier:Jupyter logo.svg|vignette|upright=0.5|Jupyter.]] Nous utilisons la distribution Anaconda : https://www.anaconda.com/ Suivez les instructions d'installation selon votre système. Choisissez « Python 3 ». {{note|Sous MacOS X, il peut être nécessaire de créer un alias vers le programme Jupyter-lab ; ce programme se trouve habituellement au chemin <code>/anaconda3/bin/jupyter-lab</code>.}} Bien entendu, vous pouvez aussi utiliser un éditeur de texte classique. Vous pouvez par exemple vous intéresser à l'éditeur Pulsar<ref>{{lien web |url=https://pulsar-edit.dev/ |titre=Pulsar Edit |site=Pulsar |consulté le=2025-09-02}}.</ref>, libre et multiplateforme. == Premiers pas == [[Fichier:Jupyter Python Save As Firefox.png|vignette|Fenêtre Python/Jupyter Lab dans Firefox : fonction <code>Save Notebook As…</code>]] Lancez le programme <code>Jupyter Lab</code>. Cela vous ouvre une fenêtre dans votre navigateur Internet. Dans cette fenêtre, choisissez <code>Notebook Python 3</code> : la page devient un éditeur de texte. Cette page est organisée en cellules ; une cellule est une zone de texte. Vous pouvez donc avoir plusieurs zones de texte, chacune contenant un élément du programme que vous développez. Dans un premier temps, nous allons travailler avec une seule cellule et l'utiliser comme une ligne de commande. Dans cette cellule, tapez <code>3 + 2</code> puis cliquez que le bouton d'évaluation <code>[▶]</code> : cela affiche logiquement le résultat « 5 » en dessous. Vous pouvez sauvegarder cet embryon de programme avec le menu <code>File &gt; Save Notebook As…</code> Cela crée un fichier portant l'extension de nom de fichier <code>.ipynb</code>. {{boîte déroulante/début|titre=Définir un répertoire de départ}} Jupyter Notebook se présente dans le navigateur Internet ''({{lang|en|web browser}})'', mais s'utilise comme un navigateur de fichiers (Windows Explorer, Finder, GNOME Commander, Konqueror…). Si l'on veut définir le répertoire de départ, il faut ouvrir Jupyter Notebook avec une commande dans l'Anaconda Prompt : # Ouvrir <code>Anaconda Prompt</code>. # Entrer la commande suivante : <code>jupyter notebook --notebook-dir=''chemin''</code>. Où <code>''chemin''</code> est le chemin d'accès au répertoire voulu. Par exemple, si l'on veut utiliser sous Microsoft Windows le répertoire <code>D:\Temp\</code>, on utilise la commande <code>jupyter notebook --notebook-dir=D:\Temp\</code>. {{boîte déroulante/fin}} == Vocabulaire == ; Classe, instance : Python est un langage orienté objet (oo). Les différents objets manipulés — fonctions, valeurs (numériques, chaînes de caractères)… — dérivent d'archétypes appelés « classe ». : Par exemple, une matrice Numpy est une mise en application de la classe <code>numpy.matrix</code>, on dit que « la matrice est une instance de la classe <code>numpy.matrix</code> ». ; Méthode : Une méthode est une fonction associée à une classe. Quand un objet <code>X</code> est une instance de cette classe, on peut avoir accès à la fonction par la la syntaxe <code>X.''méthode''()</code>. : Par exemple, la classe<code>numpy.matrix</code> possède une méthode <code>.max()</code> qui donne la valeur maximale contenue dans la matrice. Si <code>X</code> est une instance de la classe<code>numpy.matrix</code>, alors <code>X.max()</code> donne le maximum de <code>X</code>. ; Module, espace de nom : Un module est un ensemble de classes (fonctions, type de variables…) développées pour un sujet donné. : Par exemple, le module Numpy est dédié au calcule numérique, le module Scipy est dédié au calcul scientifique le module Matplotlib est dédié au tracé de données. : Un module se charge avec la commande <code>import</code> ; charger un module permet d'avoir accès à de nouveaux type de variable et à de nouvelles fonctions. : Les modules étant développés séparément, il se peut que des objets de différents modules portent le même nom. Pour éviter les confusions, on fait précéder le nom d'un objet par le nom du module d'où il vient, ou bien de son abréviation. Ainsi, la fonction sinus est fournie par les modules <code>math</code> et <code>numpy</code>, et n'est peut-être pas programmée de la même manière dans les deux modules. Si l'on charge les deux modules, on peut appeler <code>math.sin()</code> et <code>numpy.sin()</code> (ou bien <code>np.sin()</code>). == Commandes élémentaires == Nous avons de base les opérateurs mathématiques élémentaires : * <code>+</code> : addition ; * <code>-</code> : soustraction ; * <code>*</code> : multiplication ; * <code>/</code> : division ; * <code>//</code> : division euclidienne (entière) ; * <code>%</code> : reste de la division euclidienne ; * <code>**</code> : élévation à la puissance<ref>Note : l'accent circonflexe « <code>^</code> » ''({{lang|en|caret}})'', utilisé dans de nombreux langages pour l'élévation à la puissance, effectue ici un « ou exclusif » ''(XOR)'' bit à bit.</ref>. Exemples : cliquez dans la cellule et ajoutez à la suite <syntaxhighlight lang="python"> print("6/4 =", 6//4, "reste", 6%4) print(2**0.5) # racine carrée </syntaxhighlight> <code>[▶]</code> 6/4 = 1 reste 2 1.4142135623730951 La division euclidienne peut aussi se faire avec l'instruction <code>divmod()</code> : <syntaxhighlight lang="python"> a = divmod(6, 4) print("6/4 =", a[0], "reste", a[1]) </syntaxhighlight> Nous voyons ici que la commande <code>print()</code> affiche des informations à l'écran. Les chaînes de caractères peuvent être encadrées de guillemets simple <code>'</code> ou doubles <code>"</code>. Le croisillon <code>#</code> permet de mettre des commentaires. L'opérateur <code>+</code> permet de concaténer deux chaînes de caractères. L'affectation d'une variable se fait avec le signe égal : <syntaxhighlight lang="python"> a = 5 print(2*a) </syntaxhighlight> <code>[▶]</code> 10 Trois points pour terminer avec les nombres : le type réel à virgule flottante dispose de la méthode <code>.as_integer_ratio()</code> qui transforme un nombre en fraction. <syntaxhighlight lang="python"> a = 1.5 print(a.as_integer_ratio()) # (3, 2) : fraction 3/2 </syntaxhighlight> Ensuite, on peut obtenir l'infini +∞ avec <code>float("inf")</code> ou <code>float("infinity")</code> : <syntaxhighlight lang="python"> 1/float("inf") # 0.0 </syntaxhighlight> On peut également avoir le [[w:fr:NaN|''{{lang|en|not a number}}'']] avec <code>float("nan")</code>. Enfin, l'imaginaire <math>\sqrt{-1}</math> se note <code>1j</code>. De manière générale, on peut écrire <code>2j</code>, <code>3.5j</code>… Le type complexe dispose de la méthode <code>conjugate()</code> qui calcule le conjugué. <syntaxhighlight lang="python"> print(1j**2) # (-1+0j) a = 2 + 3.5j print(a.conjugate()) # (2-3.5j) </syntaxhighlight> Les commandes de type <code>''x''=</code>, où ''x'' est un opérateur (<code>+</code>, <code>-</code>, <code>*</code> ou <code>/</code>, <code>//</code>, <code>%</code>, <code>**</code>), permet de modifier une variable ''({{lang|en|in-place operator}})'' : * <code>a += 0.5</code> est équivalent à <code>a = a + 0.5</code> ; * <code>a -= 0.5</code> est équivalent à <code>a = a - 0.5</code> ; * <code>a *= 0.5</code> est équivalent à <code>a = a * 0.5</code> ; * <code>a /= 0.5</code> est équivalent à <code>a = a / 0.5</code> ; * … == Premier tracé graphique == [[Fichier:Graphe fct carre Python Matplotlib Jupyter.png|vignette|Graphe de la fonction carré avec Python/Matplotlib.]] Pour effectuer des tracés graphiques, il faut charger la bibliothèque Matplotlib ; nous choisissons l'option <code>pyplot</code> qui permet d'avoir une syntaxe similaire à Matlab. Pour utiliser cette bibliothèque, il faut écrire <code>import matplotlib.pyplot</code> ; cela donne l'accès à de nouvelles fonctions telles que <code>matplotlib.pyplot.plot()</code>. Comme il est fastidieux d'écrire <code>matplotlib.pyplot</code> avant chaque commande de cette bibliothèque, nous pouvons utiliser une abréviation, par exemple <code>plt</code>, introduite par <code>as</code> lors de l'importation. Il nous faut aussi pouvoir travailler facilement avec une liste de nombre. Nous chargeons pour cela la bibliothèque NumPy et nous décidons de l'abréger <code>np</code>. <syntaxhighlight lang="python"> import numpy as np import matplotlib.pyplot as plt x = np.arange(0, 2, 0.1) y = x**2 plt.plot(x, y, label="y = x^2") plt.xlabel("x") plt.ylabel("y") plt.title("Graphes de fonctions") plt.legend() </syntaxhighlight> La fonction <code>np.arange()</code> permet de créer un vecteur (liste de valeurs) allant de 0 à 2 avec un pas de 0,1. La fonction <code>plt.plot()</code> trace la courbe. Les fonctions <code>plt.xlabel()</code>, <code>plt.ylabel()</code> et <code>plt.title()</code> permettent de mettre des titres aux axes et au graphique. La commande <code>plt.legend()</code> affiche la légende définie dans la commande <code>plt.plot()</code>. Concernant la légende : vous pouvez avoir un rendu de type LaTeX en utilisant les balises <code>$…$</code> dans le paramètre <code>label</code> de <code>plt.plot()</code> : <syntaxhighlight lang="python"> plt.plot(x, y, label="$y = x^2$") </syntaxhighlight> Vous remarquerez que la valeur « 2 » n'est pas sur la figure : en effet, selon la logique du « tranchage » ''({{lang|en|slicing}})'', dans l'expression <code>np.arange(0, 2, 0.1)</code>, le nombre 2 est une extrémité exclue du vecteur. Pour l'intégrer, on peut mettre la première valeur qui ne figure pas, ici <code>2.1</code> mais cela pourrait poser des problèmes si l'on changeait le pas. Le module NumPy propose une fonction <code>nextafter()</code> qui indique le réel à virgule flottante le plus proche, on pourrait donc écrire <code>np.arange(0, np.nextafter(2, 3), 0.1)</code> (le nombre 3 servant juste à indiquer que l'on veut un nombre supérieur à 2). Mais le plus simple consiste à utiliser la fonction <code>np.linspace()</code> : <syntaxhighlight lang="python"> x = np.linspace(0, 2, 20) # 20 valeurs entre 0 et 2 inclus </syntaxhighlight> == Les chaînes de caractères == Une chaîne de caractères est simplement mise entre des guillemets simples <code>'…'</code> ''ou'' des guillemets doubles <code>"…"</code>. Si le texte contient une apostrophe, on utilise des guillemets doubles ; dans <code>"d'Artagnan"</code>, le « <code>'</code> » est interprété comme un caractère et non comme un délimiteur de chaîne. Et à l'inverse, si on veut utiliser des guillemets doubles dans la chaîne, on la délimite par des guillemets simples : <code>'"En garde !" dit-il'</code>. On peut aussi utiliser la barre de fraction inversée comme « caractère d'échappement » : <code>'"En garde !" s\'exclama d\'Aragnan'</code>. Pour éviter d'utiliser les échappements <code>\'</code> ou <code>\"</code>, si l'encodage du fichier est en Unicode (typiquement utf-8), il est également possible d'utiliser les caractères typographiques : ’ – « – » – “ – ”. Mais ces caractères ne sont pas disponibles facilement à partir du clavier seul. Dans une variable, les caractères sont numérotés de 0 à ''n'' – 1 (si ''n'' est le nombre de caractères de la chaîne) : <syntaxhighlight lang="python"> a = "Python" a[2] # t </syntaxhighlight> Si l'on veut extraire plusieurs caractères, on utilise deux-points, sous la forme <code>début:fin</code> mais il faut bien comprendre que les numéros correspondent en fait aux interstices entre les caractères. Ainsi, <code>0:1</code> va extraire uniquement le premier caractère (celui compris entre les interstices 0 et 1), <code>0:2</code> va extraire les deux premiers caractères… L'indice <code>-1</code> correspond à l'interstice entre le dernier et l'avant-dernier caractère. Pour résumer : <syntaxhighlight lang="text"> +---+---+---+---+---+---+ | P | y | t | h | o | n | +---+---+---+---+---+---+ 0 1 2 3 4 5 6 -6 -5 -4 -3 -2 -1 </syntaxhighlight> Par exemple <syntaxhighlight lang="python"> a = "Python" print(a[0:3]) # Pyt print(a[3:-1]) # ho </syntaxhighlight> La chaîne intégrale s'obtient avec <code>a[0:]</code> ou <code>a[:]</code>. La chaîne sauf le dernier caractère s'obtient avec <code>a[:-1]</code> ; la chaîne du troisième au dernier caractère s'obtient avec <code>a[2:]</code>. Le dernier caractère s'obtient avec <code>a[-1:]</code> ou bien <code>a[len(a)]</code>. Cette méthode qui consiste à définir des interstices est appelée « découpage en tranches, tranchage », en anglais ''{{lang|en|slicing}}''. Concernant les caractères non-alphanumériques, on peut utiliser les codes hexadécimaux des caractères Unicode avec l'échappement <code>\u</code> : le caractère U+''xxxx'' s'obtient avec <code>"\u''xxxx''</code>". Par exemple <syntaxhighlight lang="python"> print("Bonjour \u263A") # Bonjour ☺ </syntaxhighlight> On peut aussi utiliser les entités HTML : <syntaxhighlight lang="python"> import html … print(html.entities.html5["alpha;"]) # α </syntaxhighlight> L'entité HTML <code>&amp;''xxx'';</code> s'obtient par <code>html.entities.html5["''xxx'';"]</code>, donc en enlevant la perluète. == Listes et n-uplets == [[Fichier:Slicing index Python.svg|thumb|Découpage en tranche ''(slicing)'' : indexation des éléments à extraire d'une chaîne, d'une liste ou d'un n-uplet en Python.]] Une liste est une suite d'éléments numérotés. On peut mélanger des nombres et des chaînes de caractère. Pour déclarer une liste, il suffit d'écrire les éléments entre crochets et séparés des virgules. Pour extraire un élément ou un groupe d'éléments, on utilise également le tranchage. <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] print(a[2:4]) # ['c', 1] </syntaxhighlight> On peut aussi remplacer une partie de la liste, par exemple : <syntaxhighlight lang="python"> a = ["a", "b", "c", 1, 2, 3] a[3] = "foo" print(a) # ['a', 'b', 'c', 'foo', 2, 3] </syntaxhighlight> Pour ajouter un élément, on peut utiliser la concaténation <syntaxhighlight lang="python"> a = a + 4 print(a) # ['a', 'b', 'c', 'foo', 2, 3, 4] </syntaxhighlight> ou bien la « méthode » <code>append()</code> : <syntaxhighlight lang="python"> a.append(4) </syntaxhighlight> Une méthode est une fonction attachée à un type d'objet ; ici, la fonction <code>append()</code> est attachée aux listes. Pour effacer l'élément ''i'', on utilise <syntaxhighlight lang="python"> del a[i-1] </syntaxhighlight> Si on veut obtenir la valeur de l'élément avant de le supprimer : <syntaxhighlight lang="python"> valeur = a.pop([i-1]) </syntaxhighlight> Et pour effacer le premier élément dont la valeur est ''x'' : <syntaxhighlight lang="python"> a.remove(x) </syntaxhighlight> Un n-uplet, en anglais ''{{lang|en|tuple}}'', est similaire à une liste mais on ne peut pas la modifier (ajouter ou changer un élément). L'avantage est qu'elle prend moins de place en mémoire. Elle est définie simplement en séparant les éléments par des virgules. Pour plus de clarté, on peut la mettre entre parenthèses. <syntaxhighlight lang="python"> b = "a", "b", "c", 1, 2, 3 # ou bien b = ("a", "b", "c", 1, 2, 3) </syntaxhighlight> Pour faire un n-uplet d'un seul élément, il faut mettre une virgule après l'élément. Pour des raisons de lisibilité, il est conseillé d'utiliser alors des parenthèses. <syntaxhighlight lang="python"> c = ("un seul élément",) </syntaxhighlight> Notez que la commande <code>print</code> s'applique à un n-uplet, la présence de parenthèses est donc optionnelle. <syntaxhighlight lang="python"> print "a =", 4 print("a =", 4) </syntaxhighlight> == Les booléens == Les deux valeurs booléennes sont <code>True</code> et <code>False</code>. Ce sont notamment les résultats des comparaisons avec <code>==</code> (égalité), <code>!=</code> (différence), <code>&lt;</code>, <code>&gt;</code>, <code>&lt;=</code> et <code>&gt;=</code>. On peut leur appliquer les opérateurs logiques <code>not</code>, <code>and</code> et <code>or</code>. <syntaxhighlight lang="python"> a = False b = 4 <= 5 print(a and b) # False </syntaxhighlight> L'opérateur <code>&</code> est équivalent à <code>and</code> ; le tube <code>|</code> est équivalent à <code>or</code>. Comme pour les opérations arithmétiques, on peut modifier une variable avec <code>&=</code> et <code>|=</code>. Par exemple : <syntaxhighlight lang="python"> print(True & False) # False print(True - False) # True a = True a &= False # équivalent à a = a & False print(a) # False </syntaxhighlight> On a normalement pas besoin de tester un booleen : il est son propre test. Par exemple : <syntaxhighlight lang="python"> a = True if a: print("vrai") </syntaxhighlight> Va afficher « vrai ». Si l'on veut toutefois tester la valeur, on utilise alors <code>is</code> (et non pas <code>==</code> comme pour les autres types, même si cela marche aussi avec lees booléens) : <syntaxhighlight lang="python"> a = True if a is True: print("vrai") </syntaxhighlight> {{voir|[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle]]}} == Les ensembles et les dictionnaires == Un ensemble est une liste mais dont l'ordre n'a pas d'importance. Pour cela, il suffit de mettre les éléments entre accolades <code>{…}</code>. On peut aussi utiliser l'instruction <code>set()</code>. <syntaxhighlight lang="python"> set([1, 2, 3]) # {1, 2, 3} </syntaxhighlight> Les opérations possibles sont : * <code>a in E</code> : teste si un élément ''a'' fait partie de l'ensemble E (booléen) ; * <code>E|F</code> : union des ensembles E et F ; * <code>E&F</code> : intersection des ensembles E et F ; * <code>E-F</code> : éléments de E qui ne sont pas dans F ; * <code>E^F</code> : éléments qui sont dans E ou dans F mais ''pas'' dans les deux. Un dictionnaire est un ensemble de paires « (mot-clef ; valeur) ». Il s'obtient aussi avec des accolades : <code>{ "mot-clefs1" : valeur1, "mot-clefs2" : valeurs2, …}</code>. On peut aussi utiliser la commande <code>dict()</code>. <syntaxhighlight lang="python"> dico = dict(a=1, b=2, c=3) # {'a': 1, 'b': 2, 'c': 3} print(dico["b"]) # 2 </syntaxhighlight> Notez que des accolades vides <code>{}</code> créent le dictionnaire vide. == Les espaces de noms == De nombreux modules additionnels sont développés pour le langage Python. Lorsque l'on charge un module, pour appeler les fonctions ou classe qu'il apporte, il faut rajouter le nom du module devant le nom de la fonction. Ainsi, si plusieurs modules définissent des fonctions de même nom, il n'y a pas d'ambiguïté. Par exemple, si l'on veut utiliser la fonction <code>mean()</code> apporté par le module <code>numpy</code>, il faut taper <code>numpy.mean()</code>. Le préfixe du nom de fonction ou de classe est appelé l'espace de nom. Il est possible d'abréger l'espace de nom. Ainsi, ci-dessus, nous avons abrégé <code>numpy</code> en <code>np</code> et ainsi, pour appeler la fonction, il suffit d'écrire <code>np.mean()</code> == Installation et mise à jour de modules == Il est possible que certains modules soient absents de votre installation. Pour les installer, on utilise la commande <code lang="python">pip</code> ''({{lang|en|Python installer program}})'', avec la syntaxe suivante : <syntaxhighlight lang="python"> pip install nomDuModule </syntaxhighlight> Si l'on veut mettre à jour un module avec un version plus récente, on utilise : <syntaxhighlight lang="python"> pip install --upgrade nomDuModule </syntaxhighlight> == Appelable ''({{lang|en|callable}})'' == Dans Python, un « appelable » ''({{lang|en|callable}})'' est un objet qui dispose d'une méthode <code>__cal__</code>. De manière moins jargonnante, c'est une fonction, un objet que l'on peut appeler avec une paire de parenthèses. Par exemple, l'instruction <code>divmod()</code> est un appelable. == Itérable == Dans Python, un « itérable » est un objet qui peut être utilisé dans une boucle <code>for</code> (voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Structures_de_contrôle|Éléments_de_programmation &gt; Structures_de_contrôle]]''). Il s'agit de : * liste : <code>[…]</code> ; * n-uplet ''({{lang|en|tuple}})'' : <code>[…]</code> ; * chaîne de caractère : <code>"…"</code> ou <code>'…'</code> ; * d'ensemble : <code>set([…])</code> ; * de dictionnaires : <code>dict(…)</code> * d'objet <code>range()</code> ; * d'objet NumPy <code>np.array()</code> ou <code>np.matrix()</code>. Exemples : <syntaxhighlight lang="Python"> A = [1, 2, 3] for i in A: print(i, end=" ") B = ("A", "B", "C") for i in B: print(i, end=" ") C = "123" for i in C: print(i, end=" ") D = dict(a = 1, b = 2, c = 3) for i in D: print(i, end=" ") # 1 2 3 A B C a b c </syntaxhighlight> Le tranchage ''(slicing)'' est ''en général'' utilisable sur les itérables ; il ne s'applique pas aux ensembles et dictionnaires. La commande <code>enumerate()</code> renvoie la liste des éléments de l'itérable en l'associant à son indice (pour la deuxième impression, nous utilisons la définition en compréhension, voir ''[[Python pour le calcul scientifique/Éléments_de_programmation#Définition_en_compréhension|Éléments_de_programmation &gt; Définition_en_compréhension]]'') : <syntaxhighlight lang="Python"> A = ["a", "b", "c"] print(list(enumerate(A))) # [(0, 'a'), (1, 'b'), (2, 'c')] print([a for i, a in enumerate(A) if i % 2 == 0]) # filtre les indices pairs # ['a', 'c'] </syntaxhighlight> On peut utiliser un paramètre <code>start ''i''</code> pour commencer l'énumération à l'élément ''i'' + 1. La commande <code>zip()</code> permet d'itérer sur deux itérables en parallèle. <syntaxhighlight lang="Python"> A = ["a", "b", "c"] B = [6, 7, 8] for i in zip(A, B) # ('a', 6) # ('b', 7) # ('c', 8) </syntaxhighlight> On peut utiliser une liste de booléens pour extraire une partie d'un itérable, avec la fonction <code>compress()</code> du module <code>itertools</code>. <syntaxhighlight lang="Python"> from itertools import compress A = [1, 2, 3, 4, 5] booleen = [True, False, True, False, True] print(list(compress(A, booleen))) # [1, 3, 5] </syntaxhighlight> Cette fonction est beaucoup plus performante qu'une extraction itérative de type <code>[a for i, a in enumerate(A) if booleen[i]]</code> ou <code>[a for (a, b) in zip(A, booleen) if b]</code>. == Notes et références == {{références}} ---- [[../|Python pour le calcul scientifique]] &lt; [[../|↑]] &gt; [[../Premiers programmes|Premiers programmes]] {{DEFAULTSORT:Decouverte de Python et de Jupyter}} [[Catégorie:Python pour le calcul scientifique (livre)]] tgdmla932bhvo2a1dsqymyi8t7q1nwt Python pour le calcul scientifique/Manipulation de matrices 0 72889 768567 768481 2026-06-25T08:07:59Z Cdang 1202 différence array matrix 768567 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] m1f2ggcriiud0k3jxgowhgwujn2vryx 768569 768567 2026-06-25T08:17:53Z Cdang 1202 /* Rappels et complément sur le tranchage */ extraction booléenne 768569 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. On peut extraire une sous-matrice à partir d'une matrice de booléens : <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) booleen = np.logical_or((A==1) or (A==3)) print(A[booleen]) # [1 3] </syntaxhighlight> == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] l7r7himflt8fvqnwrjxegdbn6qw58wm 768585 768569 2026-06-25T09:06:54Z Cdang 1202 /* Rappels et complément sur le tranchage */ lien 768585 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. {{voir|[[Python_pour_le_calcul_scientifique/Découverte_de_Python_et_de_Jupyter#Itérable|Découverte_de_Python_et_de_Jupyter &gt; Itérable]]}} NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. On peut extraire une sous-matrice à partir d'une matrice de booléens : <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) booleen = np.logical_or((A==1) or (A==3)) print(A[booleen]) # [1 3] </syntaxhighlight> == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] 69hbi4ntiiu28vtecp1a3mppdbv4ynu 768587 768585 2026-06-25T09:15:20Z Cdang 1202 /* Rappels et complément sur le tranchage */ +exemples 768587 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. {{voir|[[Python_pour_le_calcul_scientifique/Découverte_de_Python_et_de_Jupyter#Itérable|Découverte_de_Python_et_de_Jupyter &gt; Itérable]]}} NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. On peut extraire une sous-matrice à partir d'une matrice de booléens : <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) booleen = np.logical_or(A==1, A==3) print(booleen) # [[ True False] # [ True False] # [ False False]] print(A[booleen]) # [1 3] booleen = np.logical_or(A[:, 0]==1, A[:, 0]==3) print(booleen) # [ True True False] print(A[booleen]) # [[1 2] # [3 4]] print(A[booleen][:, 1]) # [2 4] </syntaxhighlight> == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] onhaca8lax684nu1mcakdqhk4gy0q0f 768592 768587 2026-06-25T10:36:38Z Cdang 1202 /* Rappels et complément sur le tranchage */ +exemple 768592 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. {{voir|[[Python_pour_le_calcul_scientifique/Découverte_de_Python_et_de_Jupyter#Itérable|Découverte_de_Python_et_de_Jupyter &gt; Itérable]]}} NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. <syntaxhighlight lang="Python"> A = np.array([[1]]) B = np.array([[2, 3, 4]]) C = np.append(A, B, axis=1) print(A, B, C, sep="\n") # [[1]] # [[2 3 4]] # [[1 2 3 4]] D = np.array([[10, 20, 30, 40]]) E = np.append(C, D) print(E) # [[ 1 2 3 4] # [10 20 30 40]] </syntaxhighlight> On peut extraire une sous-matrice à partir d'une matrice de booléens : <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) booleen = np.logical_or(A==1, A==3) print(booleen) # [[ True False] # [ True False] # [ False False]] print(A[booleen]) # [1 3] booleen = np.logical_or(A[:, 0]==1, A[:, 0]==3) print(booleen) # [ True True False] print(A[booleen]) # [[1 2] # [3 4]] print(A[booleen][:, 1]) # [2 4] </syntaxhighlight> == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] 1b2dnwvlr7c6roodavzinwqsistk4eb 768593 768592 2026-06-25T10:37:16Z Cdang 1202 /* Rappels et complément sur le tranchage */ 768593 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. {{voir|[[Python_pour_le_calcul_scientifique/Découverte_de_Python_et_de_Jupyter#Itérable|Découverte_de_Python_et_de_Jupyter &gt; Itérable]]}} NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. <syntaxhighlight lang="Python"> A = np.array([[1]]) B = np.array([[2, 3, 4]]) C = np.append(A, B, axis=1) print(A, B, C, sep="\n") # [[1]] # [[2 3 4]] # [[1 2 3 4]] D = np.array([[10, 20, 30, 40]]) E = np.append(C, D, axis=0) print(E) # [[ 1 2 3 4] # [10 20 30 40]] </syntaxhighlight> On peut extraire une sous-matrice à partir d'une matrice de booléens : <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) booleen = np.logical_or(A==1, A==3) print(booleen) # [[ True False] # [ True False] # [ False False]] print(A[booleen]) # [1 3] booleen = np.logical_or(A[:, 0]==1, A[:, 0]==3) print(booleen) # [ True True False] print(A[booleen]) # [[1 2] # [3 4]] print(A[booleen][:, 1]) # [2 4] </syntaxhighlight> == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] 7bt9uw08v106efb4tmwaamp32r25pkh 768594 768593 2026-06-25T11:05:25Z Cdang 1202 /* Rappels et complément sur le tranchage */ np.r_, np.c_ 768594 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. {{voir|[[Python_pour_le_calcul_scientifique/Découverte_de_Python_et_de_Jupyter#Itérable|Découverte_de_Python_et_de_Jupyter &gt; Itérable]]}} NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. <syntaxhighlight lang="Python"> A = np.array([[1]]) B = np.array([[2, 3, 4]]) C = np.append(A, B, axis=1) print(A, B, C, sep="\n") # [[1]] # [[2 3 4]] # [[1 2 3 4]] D = np.array([[10, 20, 30, 40]]) E = np.append(C, D, axis=0) print(E) # [[ 1 2 3 4] # [10 20 30 40]] </syntaxhighlight> On peut utiliser <code>np.r_[A, B]</code> pour concaténer A et B en ligne ''(row), et <code>np.c_[A, B]</code> pour les concaténer en colonne. On peut extraire une sous-matrice à partir d'une matrice de booléens : <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) booleen = np.logical_or(A==1, A==3) print(booleen) # [[ True False] # [ True False] # [ False False]] print(A[booleen]) # [1 3] booleen = np.logical_or(A[:, 0]==1, A[:, 0]==3) print(booleen) # [ True True False] print(A[booleen]) # [[1 2] # [3 4]] print(A[booleen][:, 1]) # [2 4] </syntaxhighlight> == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] qip0w9jk7t0egj74b54fqvs3fy4pkki 768595 768594 2026-06-25T11:11:57Z Cdang 1202 /* Différence entre array et matrix */ matrix dépréciée 768595 wikitext text/x-wiki Les matrices sont un élément primordial du calcul scientifique sur ordinateur pour deux raisons : # L'algèbre linéaire est au cœur de nombreux calculs. # Les matrices sont l'élément de base du calcul vectorisé qui permet un gain de temps appréciable. Pour pouvoir expliter les matrices, il faut charger le module NumPy ; nous utilisons également Matplotlib pour les graphiques. Ainsi, les programmes contiennent tous au début : <syntaxhighlight lang="python"> #!/usr/bin/python3 import numpy as np import matplotlib.pyplot as plt </syntaxhighlight> == Rappels et complément sur le tranchage == Soit une liste L composée de ''n'' éléments. * Les éléments sont numérotés de 0 à ''n'' – 1. * Le premier élément s'obtient par <code lang="python">L[0]</code>. * Le i-ème élément s'obtient par <code lang="python">L[''i'' - 1]</code>. * L'avant-dernier élément s'obtient par <code lang="python">L[-2]</code>. * Le dernier élément s'obtient par <code lang="python">L[-1]</code>. * La sous-liste composée des éléments contigus ''i'' à ''j'' s'obtient par <code lang="python">L[''i'' - 1:j]</code>. Ainsi, <code lang="python">L[0:1]</code> va extraire le premier élément, <code lang="python">L[1:-1]</code> va extraire tous les éléments sauf le premier et le dernier. {{voir|[[Python_pour_le_calcul_scientifique/Découverte_de_Python_et_de_Jupyter#Itérable|Découverte_de_Python_et_de_Jupyter &gt; Itérable]]}} NumPy fournit des fonctions permettant de manipuler les matrices : * <code lang="python">np.append(A, B)</code> : fusionne les vecteurs A et B ;<br /> s'il s'agit de matrices ou de tenseurs, la fonction les « aplatit », les transforme en vecteur ;<br />si l'on veut intégrer B dans A, on utilise <code lang="python">A = np.append(A, B)</code> ; * <code lang="python">np.append(A, B, axis = ''i'')</code> : fusionne les tenseurs selon l'indice ''i'' (<code>0</code> pour le premier indice, <code>1</code> pour le deuxième…) ; * <code lang="python">np.insert(A, i, m)</code> : insère le vecteur ''m'' dans le vecteur A (ou la matrice A aplatie) à l'emplacement ''i'' ; * <code lang="python">np.insert(A, i, M, axis = ''j'')</code> : insère le tenseur M dans le tenseur A à l'emplacement ''i'' de l'indice ''j'' ; * <code lang="python">np.delete(A, I)</code> : efface les éléments définis par le tranchage I du vecteur A (ou de la matrice A aplatie) ; * <code lang="python">np.delete(A, I, axis = ''j'')</code> : efface les éléments définis par le tranchage I selon l'indice ''j'' du tenseur A. <syntaxhighlight lang="Python"> A = np.array([[1]]) B = np.array([[2, 3, 4]]) C = np.append(A, B, axis=1) print(A, B, C, sep="\n") # [[1]] # [[2 3 4]] # [[1 2 3 4]] D = np.array([[10, 20, 30, 40]]) E = np.append(C, D, axis=0) print(E) # [[ 1 2 3 4] # [10 20 30 40]] </syntaxhighlight> On peut utiliser <code>np.r_[A, B]</code> pour concaténer A et B en ligne ''(row), et <code>np.c_[A, B]</code> pour les concaténer en colonne. On peut extraire une sous-matrice à partir d'une matrice de booléens : <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) booleen = np.logical_or(A==1, A==3) print(booleen) # [[ True False] # [ True False] # [ False False]] print(A[booleen]) # [1 3] booleen = np.logical_or(A[:, 0]==1, A[:, 0]==3) print(booleen) # [ True True False] print(A[booleen]) # [[1 2] # [3 4]] print(A[booleen][:, 1]) # [2 4] </syntaxhighlight> == Différence entre ''array'' et ''matrix'' == Dans le présent chapitre, nous utilisons essentiellement deux types d'objet NumPy : les objets ''array'' (tableau) et les objets ''matrix'' (matrice). <syntaxhighlight lang="Python"> A = np.array([[1, 2], [3, 4], [5, 6]]) B = np.matrix([[1, 2], [3, 4], [5, 6]]) </syntaxhighlight> Il existe essentiellement deux différences : * les objets ''array'' peuvent être de n'importe quelle dimension : 0 (scalaire), 1 (vecteur), 2 (matrice), 3 (tenseur d'ordre 3)… alors qu'un objet ''matrix'' est nécessairement de dimension 2 ; * les opération sur les objets ''matrix'' sont par défaut des opérations matricielles ; ainsi, la multiplication <code>A * B</code> va être une multiplication terme à terme si A et B sont des ''array'', et une multiplication matricielle si A et B sont des ''matrix'' ; pour avoir la multiplication matricielle entre ''array'', il faut utiliser <code>a@b</code> ou <code>np.dot(a,b)</code> ; * si une matrice A est inversible, alors avec un objet ''matrix'', on peut utiliser la méthode <code>A.I</code> ; si A est un objet ''array'', il faut utiliser <code>np.linalg.inv(A)</code>. La classe <code>matrix</code> a été dépréciée, il est recommandé de n'utiliser que la classe <code>ndarray</code>. == Définir un tenseur == Un tenseur est similaire à une liste mais il est défini par la fonction <code>np.array()</code>. La définition et l'extraction de composante utilise la méthode du découpage en tranches ''({{lang|en|slicing}})''. '''Exemples''' <syntaxhighlight lang="python"> a = np.array([1, 3, 5, 7]) # vecteur bc = np.array([[1], [2], [3], [4]]) # matrice 4 × 1 (matrice colonne) bl = np.array([[1, 2, 3, 4]]) # matrice 1 × 4 (matrice ligne) c = np.array([[1, 2, 3], [2, 3, 4], [3, 4, 5]]) # matrice 3 × 3 d = np.array([[[1, 2, 3], [2, 3, 4]], [[10, 9, 8], [ 7, 6, 5]]]) # tenseur d'ordre 3, de dimension 3 × 2 × 2 </syntaxhighlight> Notez que dans NumPy, un vecteur n'est pas la même chose qu'une matrice ligne ou colonne. Un vecteur de dimension ''n'' est un tenseur d'ordre 1 et de dimension ''n'' ; une matrice ligne ou colonne est un tenseur d'ordre 2 et de dimension 1 × ''n'' ou ''n'' × 1. <syntaxhighlight lang="python"> a = np.array([1, 2, 3]) b = np.array([[1, 2, 3]]) c = np.array([[1], [2], [3]]) print(a.size, b.size, c.size) # 3 3 3 print(a.ndim, b.ndim, c.ndim) # 1 2 2 print(a.shape, b.shape, c.shape) # (3,) (1, 3) (3, 1) </syntaxhighlight> La fonction <code>np.arange()</code> est similaire à la fonction <code>range()</code> pour les liste ; elle génère un vecteur de réels. La fonction <code>np.linspace()</code> permet également de créer un vecteur de même type, mais on indique le dernier nombre alors que la règle du découpage en tranches fait que le nombre maximal indiqué à <code>np.arange()</code> est le premier nombre qui ne ''figure pas'' dans le vecteur.). La fonction <code>np.zeros()</code> génère une matrice nulle, <code>np.zeros_like()</code> une matrice nulle ayant les dimensions d'une matrice fournie comme modèle. De même, <code>np.ones()</code> et <code>np.ones_like()</code> crée des matrices, dont toutes les composantes sont à 1. La fonction <code>np.eye()</code> crée une matrice unité. '''Exemples''' <syntaxhighlight lang="python"> e = np.arange(0, 2, 0.1) # vecteur [0, 0.1, 0.2…, 1.8, 1.9] f = np.linspace(0, 2, 5) # 5 nombres entre 0 et 2 soit le vecteur [0, 0.5, 1, 1.5, 2] g = np.zeros(3) # vecteur nul de dimension 3 h = np.zeros((3, 3)) # matrice nulle 3 × 3 k = np.ones_like(a) # matrice de 1 de même dimension que a u = np.eye(3) # matrice unité 3 × 3 </syntaxhighlight> Le paramètre <code>dtype</code> permet de forcer le type. Par exemple <syntaxhighlight lang="python"> a = np.array([1, 2, 3], dtype="complex") k = np.ones_like(a, dtype="int") </syntaxhighlight> La commande <code>np.linspace()</code> peut créer des matrices colonne : on donne la première et la dernière ligne ; par exemple : : <syntaxhighlight lang="python"> np.linspace([1, -1], [10, -10], 4) # [[ 1. -1.] # [ 4. -4.] # [ 7. -7.] # [ 10. -10.]] </syntaxhighlight> Les commandes <code>np.geomspace()</code> et <code>np.geomspace()</code> fonctionnent comme <code>np.linspace()</code>, mais avec une progression logarithmique. La commande <code>np.geomspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de ''a'' à ''b'' alors que <code>np.logspace(a, b, n)</code> crée un vecteur (ou une matrice colonne) allant de 10<sup>''a''</sup> à 10<sup>''b''</sup>. La méthode <code>.reshape()</code> remet en forme une matrice. Par exemple, pour transformer un vecteur de dimension 9 en une matrice 3 × 3 : <syntaxhighlight lang="python"> a = np.arange(1, 10) b = a.reshape(3, 3) # ou bien directement c = np.arange(1, 10).reshape(3, 3) </syntaxhighlight> Avec la méthode <code>.reshape()</code>, on peut utiliser la valeur –1 pour une des dimensions ; sa valeur est alors automatiquement calculée en fonction du nombre d'éléments et de l'autre dimension. On peut aussi utiliser une dimension vide ; cela crée alors un vecteur. Par exemple, pour une matrice M quelconque : <syntaxhighlight lang="python"> M.reshape(-1, 1) # crée une matrice colonne M.reshape(1, -1) # crée une matrice ligne M.reshape(-1,) # crée un vecteur </syntaxhighlight> La méthode <code>.fill()</code> remplit la matrice avec un scalaire : <syntaxhighlight lang="python"> b.fill(5) # remplace les valeurs de b par la valeur 5 </syntaxhighlight> == Extraction matricielle == Si l'on veut extraire les colonnes 1 et 3 pour toutes les lignes d'une matrice M, on utilise : <syntaxhighlight lang="python"> M[:, [0, 2]] </syntaxhighlight> == Assemblage de matrices == Si l'on veut regrouper deux matrices, on utilise la commande <code>np.concatenate()</code>. On utilise le paramètre ''axis'' pour indiquer si l'on empile les matrices l'une au-dessus de l'autre (<code>axis=0</code>) ou bien l'une derrière l'autre (<code>axis=1</code>). Par exemple : <syntaxhighlight lang="python"> A = np.matrix([[1 ,2], [3, 4]]) B = np.matrix([[5 ,6], [7, 8]]) np.concatenate((A, B), axis = 0) # [[1 2] # [3 4] # [5 6] # [7 8]] np.concatenate((A, B), axis = 1) # [[1 2 5 6] # [3 4 7 8]] </syntaxhighlight> Si l'on veut transformer deux vecteurs en une matrice de deux colonnes, chaque vecteur occupant une colonne : <syntaxhighlight lang="python"> A = np.matrix([1 ,2]) B = np.matrix([3 ,4]) np.concatenate((A.reshape(-1, 1), B.reshape(-1, 1)), axis = 1) # [[1 3] # [2 4]] </syntaxhighlight> == Opérations matricielles == Les quatre opérations classiques <code lang="python>+</code>, <code lang="python>-</code> et <code lang="python>/</code> ne fonctionnent qu'entre tenseurs de mêmes dimensions et sont des opérations élément par élément ''({{lang|en|elementwise operations}})'' : * <code lang="python">(A + B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">(A - B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">(A / B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication élément par élément se fait avec : <code lang="python">np.multiply(A, B)</code> qui vaut <code lang="python">A[i, j, k] * B[i, j, k]</code>. De même, les fonctions <code lang="python>np.add()</code>, <code lang="python>np.subtract()</code>, <code lang="python>np.multiply()</code> et <code lang="python>np.divide()</code> effectuent des opérations élément par élément sur des tenseurs de mêmes dimensions : * <code lang="python">np.add(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] + B[i, j, k]</code> ; * <code lang="python">np.subtract(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] - B[i, j, k]</code> ; * <code lang="python">np.multiply(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] * B[i, j, k]</code> ; * <code lang="python">np.divide(A, B)[i, j, k]</code> vaut <code lang="python">A[i, j, k] / B[i, j, k]</code>. La multiplication matricielle, au sens de l'algèbre linéaire, se fait avec les fonctions <code lang="python">np.dot()</code> ou <code lang="python">np.matmul()</code>, ou bien avec l'opérateur <code>@</code>. <syntaxhighlight lang="python"> a = np.matrix([[1, 2], [3, 4]]) b = np.matrix([[5, 6], [7, 8]]) print(a @ b) # np.matmul(a, b) # [[19 22] # [43 50]] </syntaxhighlight> Pour un poduit scalaire : <syntaxhighlight lang="python"> A = np.matrix([1, 2, 3]) B = np.matrix([4, 5, 6]) print(np.dot(A, B.T)) # [[</nowiki>32]] print(np.dot(A, B.T)[0, 0]) # 32 </syntaxhighlight> Notez que : * pour le produit par un scalaire, les fonctions <code lang="python">np.multiply()</code> et l'opérateur <code lang="python">*</code> sont plus performants ; * la fonction <code lang="python">np.dot()</code> est plus performante pour le produit scalaire de deux vecteurs réels ; * lorsque l'on a des vecteurs complexes, la fonction <code lang="python">np.vdot()</code> fait le produit par le conjugué du premier membre (<code lang="python">np.vdot(a, b) == np.dot(a.conj(), b)</code>) ; * la fonction <code lang="python">np.matmul()</code> et l'opérateur <code lang="python">@</code> (<code lang="python">A @ B</code>) sont plus performants pour un produit matriciel. L'opérateur <code>@=</code> fait un produit matriciel en modifiant la variable elle-même (à l'image de <code>*=</code> pour les nombres). == Calcul vectorisé == Les fonctions de NumPy traitent en général les matrices en entier. Ainsi, il n'est pas nécessaire de créer une boucle pour faire défiler les indices un par un. Il en résulte un code clair et compact et surtout un plus grande rapidité d'exécution. Par exemple : <syntaxhighlight lang="python"> x = np.linspace(0, 2*np.pi, 50) # 50 points entre 0 et 2π y = np.sin(x) plt.plot(x, y) </syntaxhighlight> La variable <code>x</code> est un vecteur de 50 valeurs et il est traité en une seule passe par la fonction sinus <code>np.sin()</code>. Outre le tranchage ''({{lang|en|slicing}})'', on peut utiliser deux autres méthodes pour extraire certaines valeurs d'une matrice : * utiliser un vecteur ou une matrice d'indices, Python extrait alors les valeurs correspondant aux indices ; * utiliser un vecteur ou une matrice de booléens de même dimension que a matrice ; Python extrait alors les valeurs correspondant aux <code>True</code>, la matrice booléenne est un « masque » pour la matrice d'origine. Par exemple : <syntaxhighlight lang="python"> a = np.arange(0, 10) b = np.array([1, 3, 5, 7, 9]) c = np.array([True, True, False, False, True, False, True, False, False, True]) print(a[b], "\n", a[c]) </syntaxhighlight> Si l'on veut inverser tous les éléments d'une matrice de bolléens, il faut utiliser la fonction <code>np.logical_not()</code> '''Exercice''' Écrire un programme Python mettant en œuvre le [[w:crible d'Érathostène|crible d'Érathostène]] pour trouver les nombres premiers inférieurs à une valeur donnée. {{boîte déroulante début|Solution}} <syntaxhighlight lang="python"> #!/usr/bin/env python # coding: utf-8 import numpy as np import matplotlib.pyplot as plt # *************** # *************** # ** Fonctions ** # *************** # *************** def eratosthene(limite): # Détermine la liste des nombres premiers entre 1 et N # par le crible d'Ératosthène # Entrées : limite : nombre entier, N # Sorties : liste : vecteur d'entiers,liste des nombres premiers indices = (np.ones(limite) == 1) # vecteur de booléens tous à True # à la fin, indice(i-1) est True si i est premier, False sinon indices[0]=False # 1 n'est pas premier imax = int(limite) i = 2 # initialisation repete = (i <= imax) while repete: if indices[i-1]: jmax = int(limite/i) j = np.arange(1, jmax)+1 indices[i*j-1]=False # élimination des multiples de i test = (i*jmax == imax) i = i + 1 repete = i*i < limite # condition d'arrêt liste0 = np.arange(limite) liste = liste0[indices]+1 return liste # ************************* # ************************* # ** Programme principal ** # ************************* # ************************* print("***** Recherche de nombres premiers par le crible d'Ératosthène *****\n") nmax = eval(input("Entrez la valeur maximale : ")) resultat = eratosthene(nmax) print("\n", resultat.shape[0], "nombres premiers entre 1 et", nmax, ":\n") print(resultat) plt.plot(resultat, np.zeros_like(resultat), "|") </syntaxhighlight> L'extraction par un vecteur d'indice intervient dans l'instruction : <syntaxhighlight lang="python"> indices[i*j-1]=False </syntaxhighlight> qui élimine en une seule passe tous les multiples de ''i''. L'extraction par un vecteur booléen intervient dans l'instruction : <syntaxhighlight lang="python"> liste = liste0[indices]+1 </syntaxhighlight> qui permet d'extraire toutes la valeurs conservées en une seule passe. {{boîte déroulante fin}} == Attributs == La classe <code>ndarray</code>, qui définit les matrices, possède un certain nombre d'attributs : * <code>.shape</code> : dimensions de la matrice ; * <code>.ndim</code> : ordre du tenseur ; * <code>.size</code> : nombre d'éléments ; * <code>.dtype</code> : type des éléments. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("a", a, "\n", " ; shape :", a.shape, " ; dim : ", a.ndim, " ; size : ", a.size, " ; dtype : ", a.dtype, "\n") </syntaxhighlight> * <code>.real</code>, <code>.imag</code> : parties réelle et imaginaire de la matrice ; * <code>.flat</code> : liste des éléments de la matrice ; les éléments sont réorganisés en une liste ; * <code>.T</code> : transposée. <syntaxhighlight lang="python">a = np.arange(0, 9).reshape(3, 3) print(a) # [[0 1 2] # [3 4 5] # [6 7 8]] print(a.T) # [[0 3 6] # [1 4 7] # [2 5 8]] print(a.flat[:]) # array([0, 1, 2, 3, 4, 5, 6, 7, 8]) </syntaxhighlight> ; Ressources : Section « Attribute », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Fonctions et méthodes de base == La classe <code>ndarray</code> possède un certain nombre de méthodes : * <code>.min()</code> et <code>.max()</code> : valeurs respectivement minimale et maximale ; * <code>.ptp()</code> : amplitude « max – min » ''({{lang|en|peak to peak}})'' ; * <code>.argmin()</code> et <code>argmax()</code> : indice où se trouvent les valeurs respectivement minimale et maximale ; * <code>.sum()</code>, <code>prod()</code> : somme et produit de tous les éléments de la matrice ; * <code>.cumsum()</code>, <code>cumprod()</code> : somme et produit cumulés. <syntaxhighlight lang="python"> a = np.linspace(1, 9, 9) print("min : ", a.min(), "; max : ", a.max(), "\n", "sum : ", a.sum(), "; cumsum : ", a.cumsum(), "\n", "prod : ", a.prod(), "; cumprod : ", a.cumprod(), "\n") </syntaxhighlight> Méthodes statistiques : * <code>.mean()</code> : moyenne ; * <code>.std()</code> : écart type ''({{lang|en|standard deviation}})''. {{loupe|../Statistiques}} Extraction de données : * <code>.diagonal()</code> : vecteur contenant les éléments de la diagonale ; * <code>.flatten()</code> : matrice « aplatie », c'est-à-dire vecteur contenant les éléments réorganisés en liste ; par rapport à l'attribut <code>.flat</code>, on peut choisir le sens de linéarisation (par lignes, <code>.flatten(C)</code>, ou par colonnes, <code>.flatten(F)</code>) mais cela crée une copie, on ne peut pas par exemple s'en servir pour modifier la matrice ; * <code>.tofile()</code> : crée un fichier texte contenant les valeurs de la matrice ; par exemple, pour une matrice <code>a</code> et pour séparer les valeurs par un point-virgule : <syntaxhighlight lang="python"> a.tofile("matriceA.txt", sep=" ; ") </syntaxhighlight> * <code>.astype(''type'')</code> : copie la matrice en convertissant le type de données : <syntaxhighlight lang="python"> a = a.astype(float) # pour avoir une matrice de réels en virgule flottante a = a.astype(str) # pour avoir une matrice de chaînes de caractères </syntaxhighlight> Tri : * <code>np.sort(M, i)</code> : crée une copie et trie la matrice selon l'axe ''i'' (0 pour le premier indice, 1 pour le deuxième… la valeur par défaut est –1 pour le dernier indice, la valeur ''none'' aplatit la matrice) ; <code>np.sort(M, 0)</code> trie chaque colonne indépendamment par ordre croissant, <code>np.sort(M, 1)</code> trie chaque ligne indépendamment par ordre croissant ; * <code>M.sort(i)</code> : cette méthode trie la matrice en elle-même ; * <code>np.argsort(M, i)</code> : crée une matrice contenant le nouvel indice, selon l'axe ''i'', de chaque élément. <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) M.sort(1) # tri des lignes indépendamment -> [[1, 5, 5, 7], [2, 3, 4, 9], [1, 5, 7, 9]] </syntaxhighlight> Pour trier les lignes selon la deuxième colonne d'une matrice, tout en conservant les lignes intactes, on peut faire comme suit : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5]]) ind = np.argsort(M[:, 1]) # indices de tri selon la 2e colonne N = M[ind, :] # réarrangement des lignes -> [[9, *1*, 7, 5], [1, *5*, 7, 5], [2, *9*, 4, 3]] </syntaxhighlight> Pour faire un tri lexicographique, on utilise la fonction <code>np.lexsort()</code> en indiquant les différentes colonnes : <syntaxhighlight lang="python"> M = np.array([[1, 5, 7, 5], [2, 9, 4, 3], [9, 1, 7, 5], [2, 9, 8, 5]]) ind = np.lexsort((M[:, 3], M[:, 2], M[:, 1], M[:, 0])) # indices de tri selon la 1re, puis la 2e, puis la 3e, puis la 4e colonne N = M[ind, :] # réarrangement des lignes -> [[1, 5, 7, 5], [2, 9, 4, 3], [2, 9, 8, 5], [9, 1, 7, 5]] </syntaxhighlight> Le tri se fait par ordre croissant. Pour trier par ordre décroissant, on inverse l'ordre de la matrice en faisant une extraction avec un pas de moins un, par exemple pour inverser les lignes : <syntaxhighlight lang="python"> M = M[:, ::-1] </syntaxhighlight> Algèbre linéaire : * <code>a.dot(b)</code> : produit matriciel ''a''⋅''b'' ; on peut aussi écrire <code>a@b</code> ; * <code>.trace()</code> : trace de la matrice (somme des éléments diagonaux) ; * <code>.transpose()</code> : transpose la matrice, résultat similaire à l'attribut <code>.T</code> ; * <code>np.cross()</code> : produit vectoriel dans ℝ³. {{loupe|../Algèbre linéaire}} Matrices de booléens : * <code>.all()</code> : applique un « et » logique à toutes les valeurs de la matrice ; * <code>.any()</code> : applique un « ou » logique à toutes les valeurs de la matrice. {{loupe|../Fonctions mathématiques générales#Fonctions booléennes}} Autre méthodes : * <code>.conj()</code> : conjugué des valeurs complexes ; * <code>.nonzero()</code> : n-uplet contenant les indices des valeurs non-nulles ; * <code>.round(n)</code> : arrondit les valeurs à la ''n''-ième décimale. ; Ressources : Section « Method », {{lien web | url = https://docs.scipy.org/doc/numpy/reference/generated/numpy.ndarray.html | titre = numpy.ndarray | site = Numpy and Scipy Documentation | consulté le = 2019-03-16 }} == Propagation == Le terme « propagation » ''({{lang|en|broadcasting}})'' désigne la manière dont Python complète les matrice lorsque des dimensions manquent. Supposons que l'on veuille additionner deux matrices M<sub>1</sub> et M<sub>2</sub> de dimensions ''m''<sub>1</sub> × ''n''<sub>1</sub> et ''m''<sub>2</sub> × ''n''<sub>2</sub> différentes. Alors : * le résultat a pour dimension max(''m''<sub>1</sub>, ''m''<sub>2</sub>) × max(''n''<sub>1</sub>, ''n''<sub>2</sub>) ; * si une des dimensions vaut 1, alors les valeurs de l'autre dimension sont dupliquées ; * sinon, les dimensions manquantes pour chaque matrice sont complétées par des 1. Par exemple : : <math>\mathrm{A} = \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> : <math>5 + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 5 & 5 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> :: La matrice (5) est de dimension 1 × 1, la valeur « 5 » est donc répétée dans les deux dimensions <syntaxhighlight lang="python"> A = np.array([[1, 2], [3, 4]]) print(5 + A) # [[6 7] # [7 8]] </syntaxhighlight> : <math>(5, 4) + \mathrm{A} = \begin{pmatrix} 5 & 4 \\ 5 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5, 4]]) + A) # [[6 6] # [6 8]] </syntaxhighlight> : <math>\begin{pmatrix} 5 \\ 4 \end{pmatrix} + \mathrm{A} = \begin{pmatrix} 5 & 5 \\ 4 & 4 \end{pmatrix} + \begin{pmatrix} 1 & 2 \\ 3 & 4 \end{pmatrix} </math> <syntaxhighlight lang="python"> print(np.array([[5], [4]]) + A) # [[6 7] # [7 8]] </syntaxhighlight> == Fonctions « universelles » == Les fonctions universelles ''({{lang|en|ufunc}})'' sont les fonctions s'appliquant aux matrices, des fonctions vectorisées. ; Ressource : {{lien web | url = https://docs.scipy.org/doc/numpy/reference/ufuncs.html#available-ufuncs | titre = Available ufuncs | site = SciPy documentation | consulté le = 2019-03-21 }} == Algèbre linéaire == {{loupe|../Algèbre linéaire}} == Notes et références == {{références}} ---- [[../Graphiques|Graphiques]] &lt; [[../|↑]] &gt; [[../Polynômes|Polynômes]] [[Catégorie:Python pour le calcul scientifique (livre)]] 94zrm3h7o8gio83y69kvtpzn7xchaig Docker/Problèmes connus 0 73441 768485 753748 2026-06-24T14:55:43Z Lbocquet 124070 768485 wikitext text/x-wiki <noinclude>{{Docker}}</noinclude> == Accéder aux logs == Par exemple si un conteneur ne se lance pas ou se relance toutes les secondes, un motif plus précis qu'en console peut se trouver dans les logs. Pour le démon : tail /var/log/docker.log === Pour les conteneurs === docker compose logs Ces deux commandes acceptent l'argument "-f" pour les afficher en temps réel. === Pour un seul conteneur === <pre> docker compose logs nom_du_conteneur </pre> ou : <pre> docker logs nom_du_conteneur </pre> == Définir les droits des fichiers partagés avec le conteneur == Si le conteneur est amené à modifier les fichiers du volume, par défaut il a les droits root de la machine hôte. Pour éviter cela, on peut faire un <code>chown www-data</code> dans Dockerfile, ou utiliser <code>UID 1000</code> (si le compte utilisé par la machine hôte est local). == Récupérer l'IP d'un conteneur == <pre> docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' nom_du_conteneur # ou docker inspect --format='{{.NetworkSettings.Networks.apps.IPAddress}}' nom_du_conteneur # où "apps" est le nom du réseau </pre> == Récupérer la version de l'image d'un conteneur == <pre> docker image inspect --format '{{json .}}' "nom_du_conteneur" </pre> Sinon ça dépend de l'image à inspecter : <pre> docker inspect traefik |grep image.version docker inspect docker-apache |grep HTTPD_VERSION docker inspect docker-php |grep PHP_VERSION </pre> == Réinitialiser les conteneurs à zéro == {{attention|clear=left|Cette opération peut prendre du temps car Docker télécharge à nouveau tous les paquets ensuite.}} Linux : <pre> docker rm -f $(docker ps -a -q); docker rmi -f $(docker images -q); docker network rm $(docker network ls -q) </pre> Windows : <pre> docker rm -f $(docker ps -a -q); docker rmi -f $(docker images -q); docker network rm $(docker network ls -q) </pre> La partie ''network'' peut être exécutée indépendamment, par exemple en cas de message ''ERROR: Pool overlaps with other one on this address space''. == Messages d'erreur == === Sous Windows === ==== /usr/bin/env: 'php\r': No such file or directory ==== Utiliser "winpty". Ex : docker exec -it php7.3-fpm bash Sinon<ref>https://www.thetopsites.net/article/50789087.shtml</ref> : docker exec -it <container> bash cd bin tr -d '\015' <console >console.new mv console console.old mv console.new console ==== Certains conteneurs ne peuvent pas être lancés (timeout) ==== Vérifier que le partage Windows a bien été fait : clic droit, Settings, Resources, File Sharing, C: (puis relancer Docker Desktop). ==== Le partage de volume ne fonctionne pas sur Linux ==== Si ça n'a jamais fonctionné : ajouter son compte au groupe "docker" et redémarrer la machine hôte. ==== Le partage de volume ne fonctionne pas sur Windows ==== Si ça n'a jamais fonctionné : ajouter son compte dans le groupe "docker-users", et redémarrer la machine hôte. C'est peut-être lié à la plage d'IP de Docker, remettre celle par défaut. Si ça marchait sur Windows 10 pro dans un {{w|Active Directory}} et que ça ne fonctionne plus en dehors de l'AD ou en VPN, c'est un bug avec Docker Desktop 2.1.0.5 qui semble résolu dans la 2.1.6.1. En effet, seul un admin de l'AD peut autoriser le partage des volumes, et le port 445 doit être ouvert. Pour tester si ça marche : docker run -v c:/Users:/data alpine ls data ==== 500: {"Message":"Unhandled exception: Drive has not been shared"}' ==== Dans Docker Desktop, partager le volume concerné. ==== ''502 Bad Gateway'' dans nginx et ''Bus error'' dans les commandes PHP ==== Redémarrer Docker Desktop. Sinon c'est un processus PHP qui gonfle à outrance à cause du code. ==== ERROR: failed to create new listening socket: socket(): Address family not supported by protocol (97) ==== Relancer Docker Desktop. ==== Error: mounting wslCLIDest: stat /mnt/host/c/Program Files/Docker/Docker/resources/wsl/docker-wsl-cli.iso: no such file or directory ==== Décocher ''Use the WSL 2 based engine'' dans les options et relancer Docker Desktop<ref>https://github.com/docker/for-win/issues/6822</ref>. ==== fatal: not a git repository (or any parent up to mount point /var) Stopping at filesystem boundary (GIT_DISCOVERY_ACROSS_FILESYSTEM not set) ==== Redémarrer Docker Desktop. ==== Error response from daemon: Mount denied: The source path "mon_dossier;C" doesn't exist and is not known to Docker ==== Sous Git Bash dans Windows, il faut préfixer le chemin local par "/". Ex : <code>docker run -it --rm -v /${PWD}:/wkDir $IMAGE_TAG yarn dev</code><ref>https://stackoverflow.com/questions/50608301/docker-mounted-volume-adds-c-to-end-of-windows-path-when-translating-from-linux</ref> ==== Invalid mode /var/www ==== Le chemin d'accès dans docker-compose.yml n'est pas compris. Cela se produit pas exemple quand on met des antislashs à la place des slashs. === Sous Linux === ==== standard_init_linux.go:211: exec user process caused "no such file or directory" ==== Cela peut se produire quand des conteneurs testés sur Linux sont utilisés sur Windows. Il faut changer les retours chariots du fichier appelé par "ENTRYPOINT" dans le Dockerfile, de CRLF (Windows) vers LF (Unix). Ex : <code>dos2unix php7.4-fpm/bin/custom-docker-php-entrypoint</code> Puis reconstruire et relancer le conteneur. ===== Autres solutions ===== On peut aussi lancer "dos2unix" automatiquement depuis le dockerfile sur une copie de l'exéutable<ref>https://willi.am/blog/2016/08/11/docker-for-windows-dealing-with-windows-line-endings/</ref>. S'il s'agit d'un dépôt Git, on peut aussi le sauvegarder autrement depuis Linux<ref>https://stackoverflow.com/questions/53165471/building-docker-images-on-windows-entrypoint-script-no-such-file-or-directory</ref> : * lancer <code>git config core.autocrlf false</code> * créer un fichier .gitattributes<ref>https://git-scm.com/docs/gitattributes</ref> contenant <code>text eol=lf</code> ==== container_linux.go:349: starting container process caused "exec: \"custom-docker-php-entrypoint\": executable file not found in $PATH": unknown ==== Cela peut se produire quand des conteneurs testés sur Windows sont utilisés sur Linux. Il faut changer leur donner les droits d'exécution (<code>chmod +x</code>). Puis reconstruire et relancer le conteneur. === Pour tout OS === ==== /bin/sh: no such file or directory ==== Forcer la reconstruction : DOCKER_BUILDKIT=0 docker compose build ==== Cannot connect to the Docker daemon. Is the docker daemon running on this host? ==== sudo usermod -aG docker $USER Si ça ne marche pas, relancer docker en administrateur. ==== Cannot start service xxx: Address already in use ==== Deux processus utilisent le même port. * Si c'est deux conteneurs, dans Docker Compose, si l'un des deux avait été retiré, il était peut-être configuré en <code>restart: always</code> et il faut le remettre dans docker-compose.yml pour le stopper. * Si l'un vient de la machine hôte, l'identifier avec <code>sudo netstat -tulpn | grep ':80 '</code> puis le stopper. ==== Cannot start service xxx :driver failed programming external connectivity on endpoint ==== Impossible de lancer un conteneur sur Windows : * Soit Docker n'a pas accès au volume, et il faut cocher la case "Shared drives" dans Docker Desktop, ou lancer la commande suivante en acceptant le partage : docker run --rm -v c:/Users:/data alpine ls /data * Soit Docker n'a pas accès aux ports de ses conteneurs, et il faut fermer les processus qui les utilisent. Il peut même s'agir d'une deuxième instance de Docker. ==== Couldn't connect to Docker daemon at http+docker://localhost - is it running? ==== /etc/init.d/docker start Si le démon ne se lance pas, upgrader l'OS et redémarrer. Sinon, réinstaller Docker. ==== Could not resolve host: xxx (pas de DNS) ==== Revoir la plage d'IP définie dans le paragraphe "networks" de docker-compose.yml. ==== Device or resource busy, Cette action ne peut pas être réalisée car le fichier est ouvert dans com.docker.backend.exe ==== C'est un bug connu (sur Linux et Windows) quand {{w|Composer (logiciel)|composer}} installe certains paquets<ref>https://github.com/moby/moby/issues/22260</ref>. On ne peut supprimer le fichier qu'en fermant tout Docker (sous Windows en tout cas, il ne suffit pas de le redémarrer). Cela se produit (en cas de réécriture d'historique ?), repartir d'une branche propre avant de relancer "composer install". Sinon, le lancer dans une VM et récupérer le dossier vendor. ==== Error response from daemon: Get https://xxx: no basic auth credentials ==== Sur certains dépôts privés, pour faire un <code>docker pull</code> il faut préalablement se loguer. Ex : <pre> docker login -u mon_utilisateur -p mon_mdp mon_url </pre> ==== Invalid interpolation format for "environment" option in service "documents": "^https?://.*?$" ==== Échapper le $ interprété dans docker-compose.yml. Par exemple, remplacer : CORS_ALLOW_ORIGIN: "^https?://.*?$" par : CORS_ALLOW_ORIGIN: "^https?://.*?$$" === "encore dev-server --host my_url" ne marche pas === Mapper l'URL définie dans cette commande de package.json, avec 0.0.0.0 dans docker-compose.yml : <pre> extra_hosts: - "my_url:0.0.0.0" </pre> == Références == {{Références}} <noinclude>[[Catégorie:Messages d'erreur]]</noinclude> bm3vqnnntxbhzx6a6rid6mfa1slxn1o Le système d'exploitation GNU-Linux/Le serveur Web Nginx 0 74055 768482 691307 2026-06-24T14:48:32Z Lbocquet 124070 768482 wikitext text/x-wiki <noinclude>{{Linux}}</noinclude> == Installation == sudo apt-get install nginx == Commandes == Relancer le serveur : systemctl restart nginx Voir la configuration du processus lancé : nginx -T Voir les logs : journalctl -u nginx Activer un vhost : <syntaxhighlight lang=bash> ln -s /etc/nginx/sites-available/mon_vhost.conf /etc/nginx/sites-enabled/ systemctl reload nginx </syntaxhighlight> La différence entre "restart" et "reload", est que le premier stoppe tout puis tente de redémarrer, alors que "reload" ne stoppe les anciens processus que si le fichier de configuration ne contient pas d'erreur, et donc que les nouveaux peuvent se lancer. Il est donc plus sécurisé. == Paramètres == La configuration se trouve dans les fichiers .conf du dossier <u>/etc/nginx/conf.d/</u>. Exemples de configuration : <syntaxhighlight lang=nginx> client_max_body_size 32m; fastcgi_read_timeout 600; proxy_read_timeout 600; </syntaxhighlight> == vhosts == On peut trouver des validateurs de vhosts en ligne<ref>https://nginx.viraptor.info/</ref>. === Variables === le module ''ngx_http_core_module'' offre des variables correspondant à celles d'[[Apache]]<ref>https://nginx.org/en/docs/http/ngx_http_core_module.html</ref>. * $host * $alias * $root * $realpath_root * $document_root : $root ou $alias de la requête * $document_uri * $request_uri * $fastcgi_script_name === Mots réservés === Comme dans Apache, la priorité entre les vhosts est déterminée par l'ordre alphabétique des .conf de /etc/nginx/sites-enables. Les directives sont<ref>https://nginx.org/en/docs/http/ngx_http_core_module.html</ref> : ==== listen ==== Host et port écoutés par le vhost. Ex : <syntaxhighlight lang=nginx> listen 80; listen 443 ssl; </syntaxhighlight> ==== server_name ==== URL du vhost. * Exemple simple : <code>server_name x.example.com alias.example.com;</code> * Pour désigner un nom de serveur invalide (généralement dans le vhost ''default'') : <code>server_name _;</code> * Pour du regex, préfixer avec "~". Ex : <code>server_name ~^(x|y|z)\.example\.com$;</code> ** Le regex permet même les groupes de capture utilisables dans la suite du vhost (ex : $ma_variable) avec la syntaxe suivante<ref>https://nginx.org/en/docs/http/server_names.html</ref> : <code>(?<ma_variable>.+)</code>. Exemple avec appel : <syntaxhighlight lang=nginx> server_name ~(?<branch>.+)-preprod\.example\.com$; root /var/www/mon_app/preprod/$branch/; </syntaxhighlight> ==== root ==== Dossier du système de fichier vers lequel redirige l'URL du vhost. ==== location ==== Bloc paramétrant un chemin donné au sein de l'URL du server_name. Il peut contenir plusieurs autre mots réservés. ===== include ===== ===== internal ===== ===== deny ===== ===== expires ===== ===== fastcgi_param ===== Définit un des paramètres {{w|FastCGI}}<ref>https://www.nginx.com/resources/wiki/start/topics/examples/phpfcgi/</ref>. ==== index ==== Définit l'index à exécuter. Il peut être global ou inclus dans une ''location''. Ex : index app.php; ==== return ==== Ex : return 301 https://$host$request_uri; ==== rewrite ==== Réécriture d'URL<ref>https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/#taxing-rewrites</ref>. Ex : rewrite ^ http://example.com? permanent; ==== access_log et error_log ==== Optionnel : emplacement des logs d'accès et d'erreur. Par défaut, /var/log/nginx/access.log et error.log. ==== add_header ==== Ajoute des clés aux en-têtes HTTP. Par exemple pour les règles de protection {{w|Content Security Policy}} (CSP) contre le {{w|cross-site scripting}} (XSS) : <syntaxhighlight lang=nginx> add_header Content-Security-Policy "default-src 'self'; connect-src *; img-src 'self' data:; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; "; </syntaxhighlight> Pour la protection contre le {{w|clickjacking}} : add_header X-Frame-Options "SAMEORIGIN" always; Par ailleurs, des sites d'audit gratuits peuvent ensuite révéler s'il reste des failles. === Exemples === Pour un site HTTP : <syntaxhighlight lang=nginx> server { listen 80; server_name mon_site.localhost; root /var/www/mon_site; location / { try_files $uri /index.html =404; } } </syntaxhighlight> Pour un site IPv4 et IPv6 en HTTP et HTTPS : <syntaxhighlight lang=nginx> server { listen 80; listen [::]:80; listen 443 ssl; listen [::]:443; ssl_certificate /etc/nginx/conf.d/ssl-certs/ssl-cert-snakeoil.pem; ssl_certificate_key /etc/nginx/conf.d/ssl-certs/ssl-cert-snakeoil.key; ssl_trusted_certificate /etc/ssl/private/current.crt; server_name mon_site.localhost; root /var/www/mon_site; location / { try_files $uri /index.html =404; } } </syntaxhighlight> {{remarque|Le certificat et la clé de cryptage peuvent être générés avec [[OpenSSL]].}} === Rediriger le flux HTTP vers HTTPS === Ajouter dans la configuration la ligne commençant par "rewrite" ci-dessous : <syntaxhighlight lang="nginx"> server { listen 80; server_name nom-domaine.fr www.nom-domaine.fr; rewrite ^ https://$server_name$request_uri? permanent; } server { listen 443; server_name my.domain.com; ssl on; [....] } </syntaxhighlight> == Références == {{Références}} <noinclude>[[Catégorie:Messages d'erreur|nginx]]</noinclude> kpoycdze6rjkd8fxs49vvloon21y7sf Le système d'exploitation GNU-Linux/Erreurs connues 0 75229 768483 685351 2026-06-24T14:53:38Z Lbocquet 124070 768483 wikitext text/x-wiki <noinclude>{{Linux}}</noinclude> Messages d'erreurs avec solutione connue. == E667: Fsync failed == Le fichier est corrompu pour "vim" suite à une modification, mais pas pour les autres éditeurs. En utilisant donc un pour le ravoir. == mount.nfs: mount point /mnt/public does not exist == Lancer : <code>mkdir /mnt/public</code> avant le montage. == Please install all available updates for your release before upgrading == sudo apt update && sudo apt full-upgrade && sudo apt update && sudo do-release-upgrade Si ça ne suffit pas, désinstaller les paquets listés avec : apt list --upgradable == This account is currently not available == Par exemple pour lancer un processus en tant que www-data : su - www-data -s /bin/bash -c 'pwd' == Nagios == * Les erreurs du serveur peuvent être obtenues avec <code>tail /usr/local/nagios/var/nagios.log</code>. * Celles du client Windows dans <code>C:\Program Files\NSClient++\nsclient.log</code> (si configuré dans le nsclient.ini). === connect to address 127.0.0.1 and port 12489: Connexion refusée === Si le pare-feu est déjà ouvert à Nagios ou au port 12489, et si <code>C:\Program Files\NSClient++\nsc.ini</code> autorise déjà l'IP du serveur Nagios, et que le processus nscp.exe est bien lancé, c'est peut être qu'avec Nagios 4 il faut utiliser <code>C:\Program Files\NSClient++\nsclient.ini</code>. Normalement ensuite un <code>telnet 127.0.0.1 12489</code> depuis le client fonctionne. === Erreurs sur <code>/usr/local/nagios/var/spool/checkresults</code> === Ex : Error in configuration file '/usr/local/nagios/etc/nagios.cfg' - Line 452 (Check result path '/usr/local/nagios/var/spool/checkresults' is not a valid directory) Error: Unable to write to check_result_path ('/usr/local/nagios/var/spool/checkresults') - Permission denied En effet, <code>/usr/local/nagios/var/spool/checkresults</code> est nécessaire au lancement du processus Nagios 4, et peut être créé manuellement à cet effet : <syntaxhighlight lang="bash"> mkdir /usr/local/nagios/var/spool/ mkdir /usr/local/nagios/var/spool/checkresults/ chown nagios /usr/local/nagios/var/spool/checkresults chgrp nagios /usr/local/nagios/var/spool/checkresults </syntaxhighlight> === Error: Could not open command file '/usr/local/nagios/var/rw/nagios.cmd' for update! === Si depuis le portail web on ne peut pas planifier de maintenance à cause de cette erreur, alors que les permissions du fichier semblaient correctes, il faut modifier le fichier suivant<ref>http://alexnogard.com/error-could-not-open-command-file-usrlocalnagiosvarrwnagios-cmd-for-update/</ref> : <syntaxhighlight lang="bash"> vim /etc/init.d/nagios </syntaxhighlight> Rechercher la ligne démarrant par "chown $NagiosUser:$NagiosGroup $NagiosRunFile", puis ajouter en dessous : sleep 10 chmod 666 /usr/local/nagios/var/rw/nagios.cmd <syntaxhighlight lang="bash"> /etc/init.d/nagios restart </syntaxhighlight> === Internal Server Error === Si la page d'accueil fonctionne mais pas les vues monitoring, c'est certainement que le [[Apache/CGI|CGI]] n'arrive pas à s'exécuter. Parfois l'erreur est notée plus clairement : <code>You don't have permission to access /cgi-bin/nagios3/status.cgi on this server.</code> === It appears as though you do not have permission to view information for any of the services you requested === Dans l'interface web, passer "use_authentication=1" à 0 dans cgi.cfg. === Kernel panix === Peut se produire si la RAM est insuffisante (ex : < 512 Mo sur Ubuntu 16.04). === Network Unreachable === Si le serveur Nagios ping une machine qui fonctionne, mais qu'elle y apparait comme injoignable, c'est à cause de la différence entre le ping IPv4 et le ping6. Il faut juste modifier le ''check-host-alive'' de ''command.cfg'' en ajoutant "-4" à la fin : <syntaxhighlight lang=bash> define command{ command_name check-host-alive command_line $USER1$/check_ping -H $HOSTADDRESS$ -w 3000.0,80% -c 5000.0,100% -4 } </syntaxhighlight> === NSClient - ERROR: Could not get data for 5 perhaps we don't collect data this far back? === Un reboot du service ne change rien, il faut redémarrer l'OS. === NSClient - ERROR: Could not get value === Idem que ci-dessus. === NSClient - ERROR: Failed to get PDH valuee === Idem que ci-dessus. === NSClient - ERROR: Invalid password === Sur le client Windows, modifier dans <code>C:\Program Files\NSClient++\NSC.ini</code>, la ligne <code>password=</code>, afin qu'il corresponde à celui définit sur le serveur, dans <code>/usr/local/nagios/etc/objects/resource.cfg</code> à la ligne <code>$USER4$=</code>. Ou vice-versa. Sinon réinstaller le client, sa version n'est peut-être plus à jour. Si la commande suivante fonctionne depuis le serveur, c'est qu'il faut compléter <code>commands.cfg</code> : <syntaxhighlight lang="bash"> /usr/local/nagios/libexec/check_nt -H Mon_IP_Cliente -v USEDDISKSPACE -p 12489 -l c -s Mon_Mot_De_Passe </syntaxhighlight> === Status UNKNOWN, Status Information Utilisation: === La connexion au client Nagios fonctionne, mais le statut est flou : il faut réinstaller et reconfigurer Nagios client (problème de version avec le serveur incompatible). === Warning: Host 'xxx' has no default contacts or contactgroups defined! === Survient dans les logs au lancement de Nagios pour avertir qu'en cas d'alerte sur la machine mentionnée, personne ne sera prévenu. Pour y remédier, vérifier la ligne ''contact_groups'' dans ''template.cfg'' : <syntaxhighlight lang=bash> define host{ name xxx contact_groups admins ... } </syntaxhighlight> == nginx == === Logs === tail /var/log/nginx/error.log === Flusher le cache des redirections 301 === Dans les .conf<ref>https://docs.nginx.com/nginx/admin-guide/content-cache/content-caching/</ref> : proxy_cache_valid any 0m; === Flusher le cache DNS === sudo systemd-resolve --flush-caches === Messages d'erreur === ==== 413 Request Entity Too Large ==== Dans nginx.conf, augmenter la valeur : client_max_body_size 100M; ==== 504 timeout ==== http { fastcgi_read_timeout 300; proxy_read_timeout 300; proxy_connect_timeout 300; proxy_send_timeout 300; } ==== Access to fetch at 'https://example.com' from origin 'http://example.fr' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains multiple values ==== Dans le vhost, retirer la clause suivante (car elle se retrouve en double chez le client) : Access-Control-Allow-Origin: ==== Blocage d’une requête multiorigines (Cross-Origin Request) : la politique « Same Origin » ne permet pas de consulter la ressource distante située sur https://example.com. Raison : le recours à plusieurs en-têtes CORS « Access-Control-Allow-Origin » n’est pas autorisé. ==== add_header 'Access-Control-Allow-Origin' '*'; Si l'erreur survient sur Firefox et pas Chrome : remplacer l'étoile par les domaines d'origine. ==== closed keepalive connection ==== Ce n'est pas à proprement parler une erreur, mais une notification normale. ==== Connection refused ==== Il manque l'activation du site (ln -s /etc/nginx/sites-available/... /etc/nginx/sites-enabled). ==== directory index of "/var/www/mon_site/" is forbidden, client: 172.170.0.1, server: example.com, request: "GET / HTTP/1.1", host: "example.com" ==== Si le dossier est bien accessible en shell, il faut remplacer dans le vhost : <syntaxhighlight lang=nginx> location / { try_files $uri $uri/ /index.html index.php; } </syntaxhighlight> par : <syntaxhighlight lang=nginx> location / { try_files $uri /index.html index.php; } </syntaxhighlight> ==== ''FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream'' ou nginx propose de télécharger le fichier d'index au lieu de l'exécuter ==== Ajouter au vhost concerné le nom de son fichier d'index (et vérifier qu'il existe et est accessible avec l'utilisateur du serveur web) : index index.php app.php; Sinon, remplacer le <code>fastcgi_param SCRIPT_FILENAME</code>. Ex : fastcgi_param: SCRIPT_FILENAME $document_root$fastcgi_script_name; par : fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; Dans Symfony cela peut survenir quand on utilise le vhost de la version 4 (public/index.php) sur la 3 (web/app.php ou app_dev.php). ==== fastcgi_pass ==== ===== connect() failed (111: Connection refused) while connecting to upstream, client: 172.170.0.1, server: example.com, request: "GET / HTTP/1.1", upstream: "fastcgi://172.170.0.11:9000", host: "example.com" ===== Remplacer dans le vhost : <syntaxhighlight lang=nginx> fastcgi_pass unix:/run/php/7.3-default.sock; </syntaxhighlight> par : <syntaxhighlight lang=nginx> fastcgi_pass php7.3-fpm:9000; </syntaxhighlight> ===== host not found in upstream "php73-fpm" ''ou'' php7.3-fpm could not be resolved ''ou'' Failed at step EXEC spawning /usr/lib/php/php-fpm-socket-helper: No such file or directory ===== Changer le vhost concerné vers <code>unix:/var/run/php/php7.3-fpm.sock</code>. ===== connect() to unix:/var/run/php/php7.3-fpm.sock failed (2: No such file or directory) while connecting to upstream ===== Généralement assorti de "502 Bad Gateway" dans le navigateur. Installer php7.3-fpm ou changer le vhost vers <code>fastcgi_pass php7.3-fpm:9000;</code>. ==== File not found ==== Vérifier les logs nginx, cela peut être dû à "Permission denied". Si un chmod ne résout pas le problème, recloner l'appli. Si ça survient à chaque relance, ça vient peut-être de Git : [[Git/Débogage#File_mode_changed_from_100644_to_100755]]. ==== no resolver defined to resolve php73-fpm ==== Ajouter dans nginx.conf : resolver 127.0.0.1; ==== pread() / pwrite() "/etc/nginx/conf.d/default.conf" failed (38: Function not implemented) ==== Relancer Docker Desktop. ==== "ssl_stapling" ignored, issuer certificate not found ==== Modifier le vhost : changer de certificat ou ajouter : ssl_stapling off; ==== upstream timed out (110: Connection timed out) while reading response header from upstream, upstream: "fastcgi://unix:/var/run/php/php7.3-fpm.sock" ==== Relancer le processus php7.3-fpm. == Postfix == ==== 421 Server Busy Error ==== Trouver le goulet d'étranglement avec <code>qshape</code>. ==== 451 4.3.0 Temporary lookup failure ==== Sinon, recopier le <code>main.cf</code> ci-dessus. ==== 454 4.7.1 Relay access denied / relaying denied ==== * Commenter le bridage par adresses réseaux dans <code>/etc/postfix/main.cf</code> : mynetworks = * Sinon, vérifier que le domaine du destinataire figure bien dans <code>/etc/postfix/main.cf</code> : mydestination = * Sinon, dans <code>/etc/postfix/main.cf</code>, le paramètre ''virtual_mailbox_domains'' est vide<ref>http://postfix.traduc.org/</ref>. * Sinon, ajouter ou modifier les lignes suivantes à <code>/etc/postfix/main.cf</code> : smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated reject_unauth_destination * Sinon, recopier le <code>main.cf</code> ci-dessus. {{attention|1=Lors des tests, [[#Configuration_pour_des_envois_distants|ne pas commenter <code>relayhost =</code>]] pendant plus d'une heure sous peine de devenir spammeur à son insu.}} ==== 501 5.1.7 Bad sender address syntax ==== Modifier l'adresse de l'expéditeur (ex : dans les options Squirrel). ==== 550 relay not permitted / Sender verify failed ==== Vérifier le reverse DNS. ==== 550 unknown recipient / 550 5.1.1: Recipient address rejected: User unknown in local recipient table ==== Le domaine ou sa boite mail n'est pas installé sur le MX. S'il s'agit bien d'un utilisateur local, créer [[#Alias|un alias d'une boite existante]]. Sinon, si le serveur MX distant fonctionne par ailleurs, commenter dans <code>/etc/postfix/main.cf</code> : mydestination = Et relancer postfix. ==== Connection closed by foreign host / ou aucune commande ne répond après la connexion au SMTP ==== Si le serveur s'arrête immédiatement après son lancement, certaines erreurs sont visibles dans les logs. Sinon, recopier le <code>main.cf</code> ci-dessus. ==== dsn=4.4.1, status=deferred ==== Si les emails fonctionnent en local, mais pas depuis l'extérieur (avec la même adresse d'expéditeur), et qu'ils ne sont pas visibles dans tail -30 /var/log/mail.log Retester en ouvrant le port SMTP du pare-feu : <syntaxhighlight lang=bash> iptables -A INPUT -i eth0 -p tcp --dport 25 -j ACCEPT </syntaxhighlight> Ou sinon en ouvrant tous les ports : <syntaxhighlight lang=bash> #!/bin/sh echo "Flushing iptables rules..." sleep 1 iptables -F iptables -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -P INPUT ACCEPT iptables -P FORWARD ACCEPT iptables -P OUTPUT ACCEPT </syntaxhighlight> ==== dsn=5.4.6, status=bounced (mail for mail.mondomaine.fr loops back to myself) ==== * Le paramètre ''mydestination'' n'est pas bien définit. * Le domaine est inactif dans la table MySQL<ref>http://unix.stackexchange.com/questions/128630/postfix-email-bounced-mail-for-domain-loops-back-to-myself</ref> ==== Erreurs Dovecot ==== ===== Error: Invalid settings in userdb ===== Survient quand on se connecte par exemple au webmail en root. Cette protection incite à utiliser un autre compte, la solution la plus simples est de définir un alias dans pour root dans /etc/aliases. Si cela survient alors qu'un alias existait déjà, il suffit de relancer <code>newaliases</code> pour réparer. ===== Error: stat(/home/postmaster/Maildir/tmp) failed: Permission denied ===== chmod -R 777 /home/postmaster/Maildir/ ==== Erreurs fetchmail ==== Les symptômes suivants sont liés à la même erreur fetchmail : * POP3 : Erreur de login ou d'identification inconnue / erreur socket durant la réception * IMAP : Connexion refusée / Échec de l'autorisation * SMTP : The recipient server did not accept our requests to connect. Socket error Pour déboguer, utiliser la commande : fetchmail -v Sinon, bien vérifier que l'utilisation et le mot de passe sont entre guillemets dans : vim ~/.fetchmailrc Et le chemin vers le programme dans : vim ~/.forward Les retours de mails sont visibles dans : ls -alh /var/lib/fetchmail/Maildir/new ou : ls -alh /home/user/Maildir/new ==== Erreurs procmail ==== ===== Mails perdus / delivered to command: procmail -a "$EXTENSION" / delivered to command: IFS=' '&&exec /usr/bin/procmail -f-||exit 75 #user ===== Si les logs montrent que les mails sont bien envoyés, mais restent introuvables par ailleurs, vérifier la boite système via : procmail -v Si cela commence par ''/var/mail/'', les emails perdus sont probablement tous dans le fichiers : cat /var/mail/nobody Pour les router vers les boites des utilisateurs, revoir la configuration des <code>.forward</code> et <code>.procmailrc</code> ci-dessus, jusqu'à voir les nouveaux dans : ls -alh /home/user/Maildir/new ==== Unable to connect to remote host: Connection refused ==== /etc/init.d/postfix start ou si les logs donnent : NO [AUTHENTICATIONFAILED] Authentication failed. alors réinstaller. ==== unknown key version / dkim=temperror (no key for signature) ==== Lorsqu'on envoie par webmail, il faut qu'un logiciel insère automatiquement la signature DKIM dans chaque courrier sortant. C'est pourquoi il faut installer OpenDKIM<ref>https://sourceforge.net/projects/opendkim/files/</ref>. Lorsqu'il redémarre, le syslog peut indiquer des erreurs de permissions sur la clé qu'il faut corriger : <syntaxhighlight lang=bash> /etc/init.d/opendkim restart tail /var/log/syslog chown opendkim /etc/ssl/private/dkim.key chmod 700 /etc/ssl/private/dkim.key </syntaxhighlight> ==== warning: cannot get RSA private key from file: nomdedomaine.fr.key disabling TLS support ==== Ceci apparait dans mail.log quand on chiffre la clef privée avec un mot de passe. Il ne faut donc pas en mettre. ==== warning: connect #1 to subsystem private/proxymap: Connection refused ==== Il manque la ligne dans master.cf : proxymap unix - - n - - proxymap == Références == {{Références}} <noinclude>[[Catégorie:Messages d'erreur]]</noinclude> 8h44x6ydg9ygcdqv16xibc16gjltabd Fonctionnement d'un ordinateur/Les méthodes de synchronisation entre processeur et périphériques 0 78309 768545 765333 2026-06-24T20:17:45Z Mewtow 31375 /* Le contrôleur de périphériques */ 768545 wikitext text/x-wiki Dans ce chapitre, on va voir comment les périphériques communiquent avec le processeur ou la mémoire. On sait déjà que les entrées-sorties (et donc les périphériques) sont reliées au reste de l'ordinateur par un ou plusieurs bus. Pour communiquer avec un périphérique, le processeur a juste besoin de configurer ces bus avec les bonnes valeurs. Dans la façon la plus simple de procéder, le processeur se connecte au bus et reste connecté au bus tant que le périphérique n'a pas traité sa demande, que ce soit une lecture, ou une écriture. Mais les périphériques sont tellement lents que le processeur passe son temps à attendre le périphérique. Aussi, il a fallu trouver une solution pour simplifier la communication avec les périphériques. ==Le contrôleur de périphériques== Pour résoudre ce problème, il suffit d'intercaler un intermédiaire entre le périphérique et le reste de l'ordinateur. Cet intermédiaire s'appelle le '''contrôleur de périphériques'''. Les contrôleurs de périphérique vont du simple circuit de quelques centaines de transistors à un microcontrôleur très puissant. Le contrôleur de périphérique est généralement placé sur la carte mère, mais il peut être intégré directement dans le périphérique, tout dépend de la situation. [[File:CPT-3Box-Model-Peripherals.svg|centre|vignette|upright=1.5|Contrôleur de périphériques.]] Le processeur envoie au contrôleur de périphérique des « commandes », des valeurs numériques auxquelles le périphérique répond en effectuant un ensemble d'actions préprogrammées. Le contrôleur de périphérique reçoit les commandes envoyées par le processeur et pilote le périphérique de façon à faire ce qui est demandé. Le boulot du contrôleur de périphérique est de générer des signaux de commande qui déclencheront une action effectuée par le périphérique. L'analogie avec le séquenceur d'un processeur est possible. [[File:Controleur de périphériques.png|centre|vignette|upright=2.5|Echanges de données entre bus et contrôleur de périphériques.]] ===Les registres d’interfaçage=== Pour faire son travail, le contrôleur de périphérique doit avoir de quoi mémoriser les données à échanger entre processeur et périphérique. Pour cela, il contient des '''registres d'interfaçage''' entre le processeur et les entrées-sorties. Pour simplifier, les registres d’interfaçage sont de trois types : les registres de données, les registres de commande et les registres d'état. * Les ''registres de données'' permettent l'échange de données entre le processeur et les périphériques. On trouve généralement un registre de lecture et un registre d'écriture, mais il se peut que les deux soient fusionnés en un seul registre d’interfaçage de données. * Les ''registres de commande'' sont des registres qui mémorisent les commandes envoyées par le processeur. Quand le processeur veut envoyer une commande au périphérique, il écrit la commande en question dans ce ou ces registres. * Enfin, beaucoup de périphériques ont un ''registre d'état'', lisible par le processeur, qui contient des informations sur l'état du périphérique. Ils servent notamment à indiquer au processeur que le périphérique est disponible, qu'il est en train d’exécuter une commande, qu'il est utilisé par un autre processeur, etc. Ils peuvent parfois signaler des erreurs de configuration ou des pannes touchant un périphérique. [[File:Registres d'interfaçage.png|centre|vignette|upright=2|Registres d'interfaçage.]] Les registres d’interfaçage libèrent le processeur lors de l'accès à un périphérique, mais seulement en partie. Ils sont très utiles pour les transferts du processeur vers les périphériques. Le processeur écrit dans ces registres et fait autre chose en attendant que le périphérique ait terminé : le registre maintient les informations à transmettre tant que le périphérique en a besoin. Une écriture ou en envoi de commande simple demande donc au processeur d'écrire dans les registres d’interfaçage, rien de plus. Mais les transferts dans l'autre sens sont plus problématiques. Par exemple, imaginons que le processeur souhaite lire une donnée depuis le disque dur : le processeur envoie l'ordre de lecture en écrivant dans les registres d’interfaçage, fait autre chose en attendant que la donnée soit lue, puis récupère la donnée quand elle est disponible. Mais comment fait-il pour savoir quand la donnée lue est disponible ? De même, le processeur ne peut pas (sauf cas particuliers) envoyer une autre commande au contrôleur de périphérique tant que la première commande n'est pas traitée, mais comment sait-il quand le périphérique en a terminé avec la première commande ? Pour résoudre ces problèmes, il existe globalement trois méthodes : le ''pooling'', l'usage d'interruptions, et le ''Direct Memory Access''. La solution la plus simple, appelée ''Pooling'', est de vérifier périodiquement si le périphérique a envoyé quelque chose. Par exemple, après avoir envoyé un ordre au contrôleur, le processeur vérifie périodiquement si le contrôleur est prêt pour un nouvel envoi de commandes/données. Sinon le processeur vérifie régulièrement si le périphérique a quelque chose à dire, au cas où le périphérique veut entamer une transmission. Pour faire cette vérification, le processeur a juste à lire le registre d'état du contrôleur : un bit de celui-ci indique si le contrôleur est libre ou occupé. Le ''Pooling'' est une solution logicielle très imparfaite, car ces vérifications périodiques sont du temps de perdu pour le processeur. Aussi, d'autres solutions ont été inventées. ===Les interruptions de type IRQ=== La vérification régulière des registres d’interfaçage prend du temps que le processeur pourrait utiliser pour autre chose. Pour réduire à néant ce temps perdu, certains processeurs supportent les '''interruptions'''. Pour rappel, il s'agit de fonctionnalités du processeur, qui interrompent temporairement l’exécution d'un programme pour réagir à un événement extérieur (matériel, erreur fatale d’exécution d'un programme…). Lors d'une interruption, le processeur suit la procédure suivante : * arrête l'exécution du programme en cours et sauvegarde l'état du processeur (registres et program counter) ; * exécute un petit programme nommé '''routine d'interruption''' ; * restaure l'état du programme sauvegardé afin de reprendre l'exécution de son programme là ou il en était. [[File:Interruption processeur.png|centre|vignette|upright=2|Interruption processeur]] Dans le chapitre sur les fonctions et la pile d'appel, nous avions vu qu'il existait plusieurs types d'interruptions différents. Les interruptions logicielles sont déclenchées par une instruction spéciale et sont des appels de fonctions spécialisés. Les exceptions matérielles se déclenchent quand le processeur rencontre une erreur : division par zéro, problème de segmentation, etc. Les interruptions matérielles, aussi appelées '''IRQ''', sont des interruptions déclenchées par un périphérique et ce sont celles qui vont nous intéresser dans ce qui suit. Les IRQ sont générées par le contrôleur de périphérique quand c'est nécessaire. [[File:Contrôleur de périphérique.png|centre|vignette|upright=2|Contrôleur de périphérique.]] Avec ces IRQ, le processeur n'a pas à vérifier périodiquement si le contrôleur de périphérique a fini son travail. À la place, le contrôleur de périphérique prévient le processeur avec une interruption. Par exemple, quand vous tapez sur votre clavier, celui-ci émet une interruption à chaque appui/relevée de touche. Ainsi, le processeur est prévenu quand une touche est appuyée, le système d'exploitation qu'il doit regarder quelle touche est appuyée, etc. Pas besoin d'utiliser du ''pooling'', pas besoin de vérifier sans cesse si un périphérique a quelque chose à signaler. À la place, le périphérique déclenche une interruption quand il a quelque chose à dire. ===Les FIFO internes au contrôleur de périphérique=== Le contrôleur de périphérique peut contenir des mémoires FIFO, afin de mettre en attente les transmissions CPU<->périphérique/bus. Rappelons que les transferts se sont entre CPU et contrôleur, puis entre contrôleur et périphérique/bus. Vu que les trois composants ne sont pas synchronisés, il est nécessaire de mettre en attente des transmissions dans les registres d'interfaçage. Mais les registres d'interfaçage ne permettent que de mémoriser une seule transmission à la fois, et ce n'est pas l'idéal niveau performance. Par exemple, prenons l'exemple du circuit 8250 de National Semiconductors, utilisé sur les PC 8 bits. Il s'agissait d'un UART, à savoir d'un circuit qui recevait des octets envoyés sur une liaison série, connectées à un modem ou une imprimante. Il disposait d'un registre d'interface d'un octet en émission, et un autre en réception, ce qui permettait d'envoyer ou de recevoir des trames d'un octet chacune. Les bus auxquels il était relié ne gérait pas des trames plus longues, la transmission se faisait octet par octet. Le problème est que le 8250 générait une interruption processeur à chaque octet reçu ! En soi, ce n'était pas un problème si majeur, car les modems et imprimantes de l'époque étaient très lents, et que les liaisons série de l'époque avaient une fréquence minable. Mais rapidement, avec l'introduction de liaisons série plus rapide, l'UART 8250 générait trop d'interruptions et cela avait un cout en performances. Son successeur, le 16 550 UART, a corrigé ce problème en ajoutant une mémoire FIFO en sortie (et une en entrée, qu'on passe sous silence). Les octets reçus sont copiés non pas vers le registre d'interfaçage, mais vers une mémoire FIFO qui le précède. Elle est capable de mémoriser 16 octets reçus, dans leur ordre de réception. Une interruption est envoyée non pas à chaque octet, mais quand la mémoire FIFO est pleine. Utiliser des mémoires FIFO en réception permet d'accumuler plusieurs transmissions reçues, que le processeur lit en bloc quand il est disponible. Cela permet au processeur de lire plusieurs octets d'un coup assez rapidement, plutôt que d'être dérangé pour chaque octet ou chaque trame. Ainsi, on génère moins d'interruptions. La même méthode peut s'appliquer sans interruptions, avec la technique du ''pooling'', avec des avantages similaires. Et la même chose a lieu en émission : le contrôleur peut accepter plusieurs commandes/données consécutives, et les envoyer aux périphériques une par une. L'envoi des commandes consécutives peut se faire en un seul bloc, ou bien une par une, mais avec un rythme différent de celui du périphérique. ===Un contrôleur de périphérique peut gérer plusieurs périphériques=== Les contrôleurs de périphériques les plus simples ne sont connectés qu'à un seul périphérique, via une connexion point à point. Tel est le cas du port série RS-232 ou des différents ports parallèles, autrefois présents à l'arrière des PC. Mais de nombreux contrôleurs de périphériques sont connectés à plusieurs périphériques. Prenez par exemple l'USB : vous avez plusieurs ports USB sur votre ordinateur, mais ceux-ci sont gérés par un seul contrôleur USB. En fait, ces périphériques sont connectés au contrôleur de périphérique par un '''bus secondaire''', et le contrôleur gère ce qui transite sur le bus. On devrait plutôt parler de '''contrôleur de bus''' que de contrôleur de périphérique dans ce cas précis, mais passons. [[File:Controleur de périphérique qui adresse plusieurs périphériques.png|centre|vignette|upright=2|Contrôleur de périphérique qui adresse plusieurs périphériques]] Les périphériques connectés à un même contrôleur peuvent être radicalement différents, même s’ils sont connectés au même bus. C'est notamment le cas pour tout ce qui est des contrôleurs PCI, USB et autres. On peut connecter en USB aussi bien des clés USB, des imprimantes, des scanners, des lecteurs DVD et bien d'autres. Mais leur respect du standard USB les rend compatibles. Au final, le contrôleur USB gère le bus USB mais se fiche de savoir s’il communique avec un disque dur, une imprimante USB ou quoique ce soit d'autre. Toujours est-il que le contrôleur de périphérique doit pouvoir identifier chaque périphérique. Prenons par exemple le cas où une imprimante, une souris et un disque dur sont connectés en USB sur un ordinateur. Si je lance une impression, le contrôleur de périphérique doit envoyer les données à l'imprimante et pas au disque dur. Pour cela, il attribue à chaque périphérique une ou plusieurs adresses, utilisées pour l'identifier et le sélectionner. En général, les périphériques ont plusieurs adresses : une par registre d’interfaçage. L'adresse permet ainsi d'adresser le périphérique, et de préciser quel registre du contrôleur lire ou écrire. L'adresse d'un périphérique peut être fixée une bonne fois pour toutes dès la conception du périphérique, ou se configurer via un registre ou une EEPROM. Comme on l'a vu dans le chapitre sur les bus, la sélection du bon composant se fait de deux manières : soit les périphériques vérifient si la transmission leur est dédiée, soit on utilise du décodage partiel d'adresse. Le décodage d'adresse n'est pas utilisé quand on peut ajouter ou retirer des périphériques à la demande, la première méthode est plus pratique. Le contrôleur attribue alors une adresse à chaque composant quand il est branché, il attribue les adresses à la volée. Les adresses en question sont alors mémorisées dans le périphérique, ainsi que dans le contrôleur de périphérique. [[File:Décodage d'adresse par le contrôleur de périphérique.png|centre|vignette|upright=2|Décodage d'adresse par le contrôleur de périphérique.]] ==Les entrées d'interruption du processeur== Implémenter les interruptions matérielles demande d'ajouter des circuits à la fois sur le processeur et sur la carte mère. La méthode la plus simple demande d'ajouter au processeur une entrée d'interruption, qui est mise à 1 quand une interruption survient. Cependant, la majorité des processeurs utilise deux entrées d'interruption : une pour les interruptions masquables, et une autre pour les interruptions non-masquables. ===L'entrée d'interruption : niveaux logiques ou fronts=== Nous allons d'abord nous intéresser aux cas d'une interruption matérielle unique, c'est à dire au cas avec un seul périphérique. On peut par exemple imaginer le cas d'un thermostat, basé sur un couple processeur/RAM/ROM, relié à un capteur de mouvement, qui commande une alarme. Le processeur n'a pas besoin d'interruptions pour gérer l'alarme, mais le capteur de mouvement fonctionne avec des interruptions. Dans ce cas, on a juste besoin d'ajouter une entrée sur le processeur, appelée l''''entrée d'interruption''', souvent notée INTR ou INT. L'entrée d'interruption peut fonctionner de deux manières différentes, qui portent le nom d''''entrée déclenchée par niveau logique''' et d''''entrée déclenchée par front montant/descendant'''. Les noms sont barbares mais recouvrent des concepts très simples. Le plus simple est le cas de l'''entrée déclenchée par niveau logique'' : la mise à 1 de cette entrée déclenche une interruption au cycle d'horloge suivant. En réalité, la majorité des processeurs préfèrent mettre l'entrée INT à 0 pour déclencher une interruption, mais nous allons considérer l'inverse dans ce qui suit. Le processeur vérifie au début de chaque cycle d'horloge si cette entrée est mise à 0 ou 1 et agit en conséquence. Dans le cas le plus basique, le processeur reste en état d'interruption tant que l'entrée n'est pas remise à 0, généralement quand le processeur prévient le périphérique que la routine d'interruption est terminée. Cette solution est très simple pour détecter les interruptions, mais pose le problème de la remise à zéro de l'entrée, qui demande de communiquer avec le contrôleur de périphérique. Une autre solution consiste à utiliser des signaux d'interruption très brefs, qui mettent l'entrée à 1 durant un cycle d'horloge, avant de revenir à 0 (ou l'inverse). Le signal d'interruption ne dure alors qu'un cycle d'horloge, mais le processeur le mémorise dans une bascule que nous nommerons INT#BIT dans ce qui suit. La bascule INT#BIT permet de savoir si le processeur est en train de traiter une interruption ou non. Elle est mise à 1 quand on présente un 1 sur l'entrée d'interruption, mais elle est remise à 0 par le processeur, quand celui-ci active l'entrée Reset de la bascule à la fin d'une routine d'interruption. À l'opposé, avec une ''entrée déclenchée par front montant/descendant'', on doit envoyer un front montant ou descendant sur l'entrée pour déclencher une interruption. La remise à zéro de l'entrée est plus simple qu'avec les entrées précédentes. Si l'entrée détecte aussi bien les fronts montants que descendants, il n'y a pas besoin de remettre l'entrée à zéro. Le problème de ces entrées est que le signal d'interruption arrive pendant un cycle d'horloge et que le processeur ne peut pas le détecter facilement. Pour cela, il faut ajouter quelques circuits qui détectent si un front a eu lieu pendant un cycle, et indique le résultat au processeur. Ces circuits traduisent l'entrée par front en entrée par niveau logique, si on peut dire. Il s'agit le plus souvent d'une bascule déclenchée sur front montant/descendant, rien de plus. ===Les deux entrées d'interruption : masquable et non-masquable=== Pour rappel, le masquage d'interruption permet de retarder ou d'ignorer les interruption tant que le masquage est actif. Les interruptions ignorées/retardées sont dites, masquées comme le veut la terminologie. Le masquage des IRQ s'implémente au niveau du processeur, au niveau de l'entrée d'interruption. En cas de masquage des interruptions, il ignore simplement ce qu'il y a sur l'entrée d'interruption. Mais il doit exécuter l'interruption une fois le masquage levé, ce qui demande de mémoriser qu'une interruption a eu lieu. Pour cela, l'entrée d'interruption masquable contient une bascule qui est mise à 1 quand l'entrée d'interruption passe à 1. Elle est remise à 0 quand le processeur a pris en compte l'interruption et l'a exécutée. De plus, en sortie de la bascule, le processeur ajoute une porte ET pour combiner le contenu de la bascule avec un signal généré par le séquenceur qui dit s'il faut ou non masquer l'interruption Les '''interruptions non-masquables''' ne doivent pas être masquées, quelle que soit la situation. Les interruptions non-masquables sont généralement générées en cas de défaillances matérielles graves, qui demandent une intervention immédiate du processeur. Par exemple : une surchauffe du processeur, une défaillance de l'alimentation électrique, une erreur de parité mémoire, etc. Le résultat de telles défaillances est que l'ordinateur est arrêté/redémarré de force, ou alors affiche un écran bleu. Elles peuvent être générées par un contrôleur de périphérique, ou par des circuits placés sur la carte mère comme un watchdog timer, des circuits de détection de défaillances matérielles, des circuits de contrôle de parité mémoire, etc. Si on omet les défaillances matérielles, les interruptions non-masquables servent pour la gestion du watchdog timer, vu il y a quelques chapitres. Pour rappel, le watchdog timer est un mécanisme de sécurité qui redémarre l'ordinateur s'ils suspecte que celui-ci a planté. Le watchdog timer est un compteur/décompteur qui redémarre le système s'il déborde. Mais une interruption non-masquable réinitialise le watchdog timer régulièrement, ce qui signifie que le système n'est pas censé redémarrer. Pour gérer les interruptions non-masquables, beaucoup de processeurs ont deux entrées d'interruption séparées : une pour les interruptions masquables, une autre pour les interruptions non-masquables. C'est le cas des premiers processeurs x86 des PCs, qui disposent d'une entrée INTR pour les interruptions masquables, et une entrée NMI pour les interruptions non-masquables. La différence entre les deux est l'usage ou non de la bascule mentionnée plus haut. L'entrée d'interruption normale dispose de cette bascule pour le masquage, alors que l'entrée d'interruption non-masquable ne l'a pas. ==Le contrôleur d'interruption== Précédemment, nous avons vu le cas où nous n'avons qu'un seul contrôleur de périphérique dans l’ordinateur. Mais avec plusieurs périphériques, l'implémentation des interruptions matérielles est plus compliqué. Dans une implémentation simple des IRQ, chaque contrôleur de périphérique envoie ses interruptions au processeur via une entrée dédiée. Mais cela demande de brocher une entrée d'interruption par périphérique, ce qui limite le nombre de périphériques supportés. Et c'est dans le cas où chaque périphérique n'a qu'une seule interruption, mais un périphérique peut très bien utiliser plusieurs interruptions. Par exemple, un disque dur peut utiliser une interruption pour dire qu'une écriture est terminée, une autre pour dire qu'il est occupé et ne peut pas accepter de nouvelles demandes de lecture/écriture, etc. [[File:Entrées d'interruptions séparées pour chaque périphérique.png|centre|vignette|upright=2|Entrées d'interruptions séparées pour chaque périphérique]] Une autre possibilité est de connecter tous les périphériques à l'entrée d'interruption à travers une porte OU ou un OU câblé, mais elle a quelques problèmes. Déjà, cela suppose que l'entrée d'interruption est une entrée déclenchée par niveau logique. Mais surtout, elle ne permet pas de savoir quel périphérique a causé l'interruption, et le processeur ne sait pas quelle routine exécuter. [[File:Entrée d'interruption partagée.png|centre|vignette|upright=2|Entrée d'interruption partagée]] Pour résoudre ce problème, il est possible de modifier la solution précédente en ajoutant un ''numéro d'interruption'' qui précise quel périphérique a envoyé l'interruption, qui permet de savoir quelle routine exécuter. Au lieu d'avoir une entrée par interruption possible, on code l'interruption par un nombre et on passe donc de <math>N</math> entrées à <math>log_2{(N)}</math> entrées. Le processeur récupère ce numéro d'interruption, qui est généré à l'extérieur du processeur. Pour implémenter cette solution, on a inventé le '''contrôleur d'interruptions'''. C'est un circuit qui récupère toutes les interruptions envoyées par les périphériques et qui en déduit : le signal d'interruption et le numéro de l'interruption. Le numéro d'interruption est souvent mémorisé dans un registre interne au contrôleur d'interruption, souvent mappé en mémoire. Il dispose d'une entrée par interruption/périphérique possible et une sortie de 1 bit qui indique si une interruption a lieu. Il a aussi une sortie pour le numéro de l'interruption, qui permet au processeur de lire le numéro d'interruption. [[File:Contrôleur d'interruptions IRQ.png|centre|vignette|upright=3|Contrôleur d'interruptions IRQ]] L'intérieur d'un contrôleur d'interruption n'est en théorie pas très compliqué. Déterminer le signal d'interruption demande de faire un simple OU entre les entrées d'interruptions. Déduire le numéro de l'interruption demande d'utiliser un simple encodeur, de préférence à priorité. Pour gérer le masquage, il suffit d'ajouter un circuit de masquage en amont de l'encodeur, ce qui demande quelques portes logiques ET/NON. ===La contrôleur d'interruption est une entrée-sortie comme une autre=== Pour récupérer le numéro d'interruption, le processeur doit communiquer avec le contrôleur d'interruption. Pour cela, le contrôleur d'interruption est techniquement traité comme n'importe quel périphérique. Le registre pour le numéro d'interruption est simplement mappé en mémoire RAM, à savoir qu'il est lisible à une adresse bien précise. Et on peut aller plus loin : tous les registres du contrôleur d'interruption sont mappés en mémoire, le contrôleur d'interruption est adressable comme tout périphérique mappé en mémoire. Le numéro d'interruption est alors toujours mémorisé dans un registre interne au contrôleur d'interruption, et le processeur lit ce registre en passant par le bus de données. Dans le cas le plus simple, le numéro d'interruption est envoyé au processeur sur un bus dédié. Le défaut de cette technique est qu'elle demande d'ajouter des broches d'entrée sur le processeur. Une autre solution connecte le contrôleur d'interruption sur le bus système. Les deux solutions sont des détails d'implémentation. Mais dans les deux cas, le contrôleur d'interruption doit être connecté sur le processeur, sur son entrée d'interruption au minimum, via des fils dédiés. Le résultat est un mélange entre bus dédié et connexion au bus système/IO. Nous verrons cela avec l'exemple qui va suivre. Pour rentrer dans le détail, étudions le cas du contrôleur d'interruption 8259 couplé à un processeur Intel 486. L'Intel 8259 était un contrôleur d'interruption utilisé dans les premiers PC. Il était présent sur toutes les cartes mères des PC de l'époque, il a vraiment eu son heure de gloire. Il gérait 8 interruptions, grâce à 8 entrées IRQ et une sortie d'interruption. C'est peu, mais laissons cela de côté pour le moment. Il était connecté sur le bus de données du processeur, qui servait de bus système. En plus des 8 entrées IRQ, et des entrées CAS qu'on laisse de côté pour le moment, il disposait de broches spécialisées pour communiquer avec le processeur. * La sortie INTR permettait d'envoyer une interruption au processeur. * L'entrée INTA (''Interrupt Acwnoledge'') permettait au processeur de dire qu'il avait bien pris en compte l'interruption, ce qui permettait au 8259 de remettre sa sortie INTR à zéro. * Les deux signaux RD et WR permettaient au processeur de lire/écrire les registres du 8259. La broche WR était mise à 0 quand le processeur envoyait une commande au 8259, RD était mis à 0 quand il lisait ses registres. * Un bus de 8 bits permettait de connecter le contrôleur d'interruption au bus de données. Le 8259 était traité comme une entrée-sortie tout ce qu'il y a de plus banale. Il était connecté sur le bus système, au même titre que les entrées-sorties, la mémoire, etc. Il n'y avait pas de bus dédié, au-delà des broches INTR, INTA. Il est donc connecté aux circuits qui administrent le bus système, qui est lui-même relié au processeur. Vu que ses registres étaient mappés en mémoire RAM, il était relié au circuit décodeur d'adresse, que nous verrons en détail dans le prochain chapitre. Pour rappel, ce décodeur d'adresse connecte/déconnecte les entrées-sorties du bus, suivant les adresses envoyées dessus. Si l'adresse envoyée est destinée à la RAM ou une ROM, les entrées-sorties sont déconnectées du bus, en mettant à zéro leur signal CS (''Chip Select''). Si l'adresse est destinée à une entrée-sortie, elle est connectée sur le bus, en mettant son signal CS à 1. Le 8258 avait une entrée CS connectée directement à ce décodeur d'adresse, comme toutes les autres entrées-sorties. Il avait aussi une entrée d'adresse de un bit, qui permettait d'adresser les registres internes au 8259. Notons que le bus dédié était un bus de 8 bits, alors que les processeurs Intel de l'époque étaient des processeurs 16 ou 32 bits. Leur bus de données faisait donc 16 ou 32 bits, ce qui ne collait pas avec les 8 bits du 8259. Il y avait donc un circuit pour faire l'interface entre les deux, qui est représenté ci-dessous avec deux circuits : une mémoire tampon de 32 bits et un circuit de commande de ''byte swap''. [[File:Intel486 with 82C59A.png|centre|vignette|upright=2|Intel486 with 82C59A]] ===Les contrôleurs d'interruption en cascade=== Un contrôleur d'interruption permet de gérer un nombre limité d'interruptions. Par exemple, l'ancien Intel 8259 gérait 8 interruptions avec 8 entrées IRQ et une sortie d'interruption. Même pour l'époque, ce n'était pas assez. Un PC contenait plus de 8 périphériques, en tenant compte de la ''real time clock'' et d'autres composants intégrés sur la carte mère. Aussi, il fallait trouver une solution pour gérer plus de 8 interruptions. Et la solution a été d'utiliser plusieurs contrôleurs d'interruption en cascade, à savoir que le second contrôleur d'interruption émettait une interruption vers le premier. C'était le cas avec les premiers processeurs d'Intel dès le 8086, notamment le 486, utilisaient deux contrôleurs Intel 8259 : un contrôleur maitre, un contrôleur esclave. Le contrôleur esclave gérait les interruptions liées au bus ISA, le bus pour les cartes d'extension utilisé à l'époque, alors que le contrôleur maitre gérait le reste. Leur sortie d'interruption était soit connectée au processeur (pour le contrôleur maître), soit au contrôleur maître (pour le contrôleur esclave). Le tout permettait de gérer 15 interruptions : 8 interruptions pour le contrôleur esclave, 7 pour le contrôleur maitre (une des entrées était prise par le contrôleur esclave). Le fonctionnement était le suivant. Si une interruption avait lieu en dehors du bus ISA, le contrôleur maitre gérait l'interruption. Mais si une interruption avait lieu sur le bus ISA, le contrôleur esclave recevait l'interruption, générait un signal transmis au contrôleur maitre sur l'entrée IRQ 2, qui lui-même transmettait le tout au processeur. Le processeur accédait alors au bus qui le reliait aux deux contrôleurs d'interruption, et lisait le registre pour récupérer le numéro de l'interruption. [[File:Intel486 and cascading PIC.png|centre|vignette|upright=2|Contrôleurs d'interruptions IRQ du 486 d'Intel.]] [[File:Intel 8259.svg|vignette|Intel 8259]] En théorie, jusqu'à 8 contrôleurs 8259 peuvent être mis en cascade, ce qui permet de gérer 64 interruptions. Il faut alors disposer de 8 contrôleurs esclaves et d'un contrôleur maitre. La mise en cascade est assez simple sur le principe : il faut juste envoyer la sortie INTR de l'esclave sur une entrée d'IRQ du contrôleur maitre. Il faut cependant que le processeur sache dans quel 8259 récupérer le numéro de l'interruption. Pour cela, l'Intel 8259 disposait de trois entrées/sorties pour permettre la mise en cascade, nommées CAS0, CAS1 et CAS2. Sur ces entrées/sorties, on trouve un identifiant allant de 0 à 7, qui indique quel contrôleur 8259 est le bon. On peut le voir comme si chaque 8259 était identifié par une adresse codée sur 3 bits, ce qui permet d'adresser 8 contrôleurs 8259. Les 8 valeurs permettent d'adresser aussi bien le maitre que l'esclave, sauf dans une configuration : celles où on a 1 maitre et 8 esclaves. Dans ce cas, on considère que le maitre n'est pas adressable et que seuls les esclaves le sont. Cette limitation explique pourquoi on ne peut pas dépasser les 8 contrôleurs en cascade. Les entrées/sorties CAS de tous les contrôleurs 8259 sont connectées entre elles via un bus, le contrôleur maitre étant l'émetteur, les autres 8259 étant des récepteurs. Le contrôleur maitre émet l'identifiant du contrôleur esclave dans lequel récupérer le numéro de l'interruption sur ce bus. Les contrôleurs esclaves réagissent en se connectant ou se déconnectant du bus utilisé pour transmettre le numéro d'interruption. Le contrôleur adéquat, adressé par le maitre, se connecte au bus alors que les autres se déconnectent. Le processeur est donc certain de récupérer le bon numéro d'interruption. Lorsque c'est le maitre qui dispose du bon numéro d'interruption, il se connecte au bus, mais envoie son numéro aux 8259 esclaves pour qu'ils se déconnectent. Le fait de mettre en cascade deux 8259 a fonctionné pour les PC avec un bus ISA. Mais avec l'introduction du bus PCI, les choses sont devenues bien plus complexes. Le nombre de périphériques, et donc d'interruptions, a grimpé en flèche. Le même problème de manque d'interruption s'est fait sentir, et la solution a été similaire : rajouter un contrôleur d'interruption en cascade. Sauf que pour le bus PCI, on n'a pas rajouté un 8259, mais un autre contrôleur d'interruption dédié au bus PCI, appelé le PIR (''Programmable Interrupt Request''). Ce contrôleur d'interruption émettait 4 fils vers le 8259 esclave, connectés aux entrées d'IRQ numéro 5, 9, 10, et 11. ===Les interruptions sur les systèmes multicœurs et multi-processeurs=== L'arrivée des systèmes avec plusieurs processeurs, plusieurs cœurs, a demandé d'adapter les contrôleurs d'interruption. Et en plus d'adapter le tout aux systèmes multicœurs, il a été décidé de simplifier les contrôleurs d'interruption, en réduisant leur mise en cascade. Pour cela, les deux 8259 ont été remplacés par un unique contrôleur d'interruption, appelé l''''APIC''' (''Advanced PIC''). Je dis un contrôleur d'interruption, mais l'APIC est en réalité un standard qui peut être respecté par des contrôleurs d'interruption très différents. Si les plus simples se contentent de remplacer deux 8259 en cascade, d'autres vont plus loin et intègrent carrément un PIC pour le bus PCI ! Les tout premiers APIC avaient 16 entrées d'interruption et se contentaient de remplacer à la lettre les deux 8259. Le PIC pour le bus PCI était toujours présent. Par la suite, d'autres APIC sont apparus, avec 24 ou 32 entrées d'interruption. Typiquement, les 16 premières entrées d'interruptions étaient réservées pour remplacer/émuler les deux 8259, le reste était utilisable avec plus ou moins de flexibilité. Par exemple, un APIC avec 32 entrées d'interruption peut utiliser 16 entrées rien que pour le bus PCI, ce qui permet parfois de se passer de PIC. Un exemple d'APIC était le 82093AA. Il disposait de 24 entrées d'IRQ : 13 pour le bus ISA, 4 pour le bus PCI, une entrée d'interruption pour mettre des contrôleurs d'interruption en cascade, le reste était plus ou moins spécialisé. Le standard impose la présence de plusieurs contrôleurs d'interruptions. Chaque cœur/processeur a son propre contrôleur d'interruption, avec un contrôleur général. Le contrôleur d'interruption général est nommé ''IO-APIC'', les autres sont les APIC locaux (''local-APIC''). L'''IO-APIC'' reçoit les interruptions provenant des périphériques, et les redirige vers les processeurs/cœurs adéquats. Pour cela, sont tous reliés soit par un bus APIC dédié, soit par le bus système ou un bus existant. [[File:Contrôleurs d'interrptions sur systèmes x86 multicoeurs.png|centre|vignette|upright=1.5|Contrôleurs d'interruptions sur systèmes x86 multicœurs.]] Le premier APIC était le 82489DX. Il était prévu pour les systèmes avec deux cœurs, deux processeurs. Il regroupait l'IO-APCI et deux APIC locaux en un seul circuit. Mais cette solution a ensuite été modifiée, les APIC locaux ont été intégrés aux processeurs/cœurs eux-mêmes, ne laissant que l'IO-APCI sur la carte mère, dans son ''chipset''. Le standard APIC a évolué pour donner l'xAPIC et le x2APIC. La différences principales sont que le nombre de processeurs/cœurs supporté est passé respectivement à 256, puis à 2^32. ===Les ''Message Signaled Interrupts''=== Les interruptions précédentes demandent d'ajouter un fil par périphérique, pour que celui-ci signale une interruption au contrôleur d'interruption. Prenons l'exemple d'une carte mère qui dispose de 5 ports ISA, ce qui permet de connecter 5 périphériques ISA sur la carte mère. Chaque port ISA a un fil d'interruption pour signaler que le périphérique veut déclencher une interruption, ce qui demande de relier 5 fils au contrôleur de périphérique. Cela n'a pas l'air grand-chose, mais rappelons qu'une carte mère moderne gère facilement une dizaine de bus différents, donc certains pouvant connecter une demi-dizaine de composants. Le nombre de fils à câbler est alors important. Il existe cependant un type d'interruption qui permet de se passer de ces fils d'interruptions : les '''interruptions signalées par message''', ''Message Signaled Interrupts'' (MSI) en anglais. Elles sont utilisées sur le bus PCI et son successeur, le PCI-Express, deux bus très importants et utilisés pour les cartes d'extension dans presque tous les ordinateurs modernes et anciens. Aussi, nous allons prendre l'exemple du bus PCI Express dans ce qui suit, même si les explications fonctionnent pour d'autres bus. Les interruptions signalées par message sont déclenchées quand le périphérique écrit dans une adresse allouée spécifiquement pour, appelée une ''adresse réservée''. Le contrôleur d'interruption surveille en permanence ce qui est transmis sur le bus PCI Express. Dès qu'il voit passer une écriture à cette adresse réservée, il déclenche une interruption sur le processeur. Le reste du traitement de l'interruption est le même qu'avec les IRQ. Le processeur ayant reçu l'interruption communique alors avec le contrôleur d'interruption pour savoir quel périphérique a déclenché une interruption, récupérer le numéro de l'interruption, etc. : Le processeur a toujours une entrée d'interruption unique reliée, reliée au contrôleur d'interruption, ce sont les fils entre périphérique et contrôleur d'interruption qui disparaissent. Une implémentation possible place les adresses réservées dans le contrôleur d'interruption. Les adresses réservées correspondent à des registres du contrôleur d'interruption, qui sont mappés en mémoire. Toute écriture dans cette adresse réservée modifiera donc un registre du contrôleur d'interruption. La détection de l'écriture dans une adresse réservée est donc particulièrement simple. Il y a une adresse réservée par interruption, ce qui fait que le contrôleur d'interruption détermine le numéro de l'interruption à partir de l'adresse réservée utilisée/écrite. Le périphérique écrit un message dans l'adresse réservée, qui donne des informations sur l'interruption : le numéro d'interruption au minimum, souvent d'autres informations. Un avantage est que cela permet de gérer un grand nombre d'interruptions assez facilement. On n'est plus limité par un nombre de fils, la limite tient dans le nombre d'adresses réservées. Et les controleurs d'interruptions peuvent facilement gérer un grand nombre d'adresse, tant qu'ils ont le hardware pour. L'ancien PCI 2.2 gère jusqu'à 32 adresses réservées, le bus PCI 3.0 MSI-X alloue 2048 adresses réservées. En comparaison le bus PCI gérait maximum 4 IRQ câblées, du fait des limitations en termes de fils (deux fils maximum). Un avantage lié au précédent est que cela simplifie la gestion des interruptions pour le processeur. Les 4 interruptions du bus PCI étaient partagées entre périphériques PCI, ce qui fait que le processeur avait du mal à déterminer quel périphérique avait lancé une interruption. Quand deux périphériques PCI partageaient une interruption, le processeur avait du mal à savoir lequel des deux lançait l'interruption. Mais avec les MSI, on a assez d'interruption pour ne pas avoir à les partager entre périphériques. Chaque périphérique a ses interruptions, ses adresses réservées, il ne marche pas sur les pieds des autres périphériques. Un autre avantage est que les interruptions passent par le bus PCI Express. La conséquence est que sur les systèmes multicoeurs/multi-processeurs, il n'y a pas besoin d'utiliser de contrôleur d'interruption général, d'IO-APIC. La présence d'APIC locaux est toujours nécessaire, mais l'IO-APIC n'est gardé que par souci de compatibilité avec les vieilles interruptions PCI. ===Les généralités sur les interruptions matérielles=== Peu importe que les interruptions soient implémentées avec une entrée d'interruption ou avec une signalisation par messages, certaines fonctionnalités sont souvent implémentées par le contrôleur d'interruption. Par exemple, il est possible de prioriser certaines interruptions sur les autres, ce qui permet de gérer le cas où plusieurs interruptions ont lieu en même temps. De même, il est possible de mettre en attente certaines interruptions, voire de les désactiver. En premier lieu, voyons pourquoi il est pertinent de prioriser certaines interruptions plutôt que d'autres. Quand plusieurs interruptions se déclenchent en même temps, on ne peut en exécuter qu'une seule. Pour gérer des interruptions simultanées, un '''système de priorité d'interruption''' est mis en place, où certaines interruptions sont prioritaires sur les autres. Par exemple, l'interruption qui gère l'horloge système est prioritaire sur les interruptions en provenance de périphériques lents comme le disque dur ou une clé USB. Quand plusieurs interruptions souhaitent s'exécuter en même temps, on exécute d'abord celle qui est la plus prioritaire, les autres sont alors mises en attente. La gestion des priorités est gérée par le contrôleur d'interruption, ou par le processeur si le contrôleur d'interruption est absent. Pour gérer les priorités, l'encodeur présent dans le contrôleur de périphérique doit être un encodeur à priorité, et cela suffit. On peut configurer les priorités de chaque interruption, à condition que l'encodeur à priorité soit configurable et permette de configurer les priorités de chaque entrée. Ensuite, il faut aussi parler du '''masquage des interruptions''', qui pour rappel, permet de désactiver temporairement les interruptions. Si le masquage est temporairement activé, les interruptions sont soit ignorées, soit mise en attente et exécutées une fois le masquage levé. Le masquage peut être total, dans le sens où toutes les interruptions masquables sont masquées en bloc, ce qui revient à désactiver les interruptions (sauf les non-masquables). On parle alors de masquage global. Mais il est aussi possible de ne masquer qu'une partie des interruptions masquables. Par exemple, on peut ignorer l'interruption numéro 5 provenant du disque dur, mais pas l'interruption numéro 0 du ''watchdog timer'', alors que les deux sont masquables. On parle alors de '''masquage d'interruption sélectif'''. L'avantage est que cela permet de simplifier l'implémentation des interruptions masquables et non-masquables. Le contrôleur d'interruption gère de lui-même le masquage des interruptions. Dans le cas le plus simple, où on masque toutes les interruptions en bloc, il suffit d'ajouter un '''bit MASK''' dans le contrôleur d'interruption, qui dit si les interruptions sont masquées ou non. La valeur du bit est combinée avec les signaux d'interruptions pour calculer le signal à envoyer sur l'entrée d'interruption finale. Notons que le contrôleur n'utilise ce bit que pour les interruptions non-masquables. Il dispose de deux sorties : une pour l'entrée d'interruption normale, l'autre pour l'entrée d'interruption masquable. Le bit MASK n'agit que sur la sortie pour les interruptions masquables, pas l'autre. [[File:Masquage global des interruptions dans le controleur d'interruption.png|centre|vignette|upright=2|Masquage global des interruptions dans le controleur d'interruption]] Masquer les interruptions individuellement demande quelques adaptations. Mais l'idée est globalement d'avoir un bit MASK pour chaque interruption, de le dupliquer en autant d'interruption masquables existantes, et de les regrouper dans un registre. Pour cela, le contrôleur d'interruption (ou le processeur) disposent d'un registre appelé le '''registre de masque d'interruption'''. Chaque bit de ce registre est associé à une interruption : le bit numéro 0 est associé à l'interruption numéro 0, le bit 1 à l'interruption 1, etc. Si le bit est mis à 1, l'interruption correspondante est ignorée. Inversement, si le bit est mis à 0 : l'interruption n'est pas masquée. ==Le ''Direct memory access''== Avec les interruptions, seul le processeur gère l'adressage de la mémoire. Impossible à un périphérique d'adresser la mémoire RAM ou un autre périphérique, il doit forcément passer par l'intermédiaire du processeur. Pour éviter cela, on a inventé le ''bus mastering'', qui permet à un périphérique de lire/écrire sur le bus système. C'est suffisant pour leur permettre d'adresser la mémoire directement ou de communiquer avec d’autres périphériques directement, sans passer par le processeur. Le '''Direct Memory Access''', ou DMA, est une technologie de ''bus mastering'' qui permet de copier un bloc de mémoire d'une source vers la destination. La source et la destination peuvent être la mémoire ou un périphérique, ce qui permet des transferts mémoire -> mémoire (des copies de données, donc), mémoire -> périphérique, périphérique -> mémoire, périphérique -> périphérique. Le bloc de mémoire commence à une adresse appelée adresse de départ, et a une certaine longueur. Soit il est copié dans un autre bloc de mémoire qui commence à une adresse de destination bien précise, soit il est envoyé sur le bus à destination du périphérique. Ce dernier le reçoit bloc pièce par pièce, mot mémoire par mot mémoire. Sans DMA, le processeur doit copier le bloc de mémoire mot mémoire par mot mémoire, byte par byte. Avec DMA, la copie se fait encore mot mémoire par mémoire, mais le processeur n'est pas impliqué. ===Le contrôleur DMA=== Avec le DMA, l'échange de données entre le périphérique et la mémoire est intégralement géré par un circuit spécial : le '''contrôleur DMA'''. Il est généralement intégré au contrôleur de périphérique et placé sur la carte mère, parfois intégré au périphérique, parfois intégré au processeur, mais est toujours connecté au bus mémoire. Le contrôleur DMA contient des registres d'interfaçage dans lesquels le processeur écrit pour initialiser un transfert de données. Un transfert DMA s'effectue sans intervention du processeur, sauf au tout début pour initialiser le transfert, et à la fin du transfert. Le processeur se contente de configurer le contrôleur DMA, qui effectue le transfert tout seul. Une fois le transfert terminé, le processeur est prévenu par le contrôleur DMA, qui déclenche une interruption spécifique quand le transfert est fini. Le contrôleur DMA contient généralement plusieurs compteurs : un pour l'adresse de la source, un pour l'adresse de destination, un autre pour le nombre de bytes restants à copier. Les deux compteurs d'adresse sont initialisé avec l'adresse source/destination de départ, à savoir l'adresse du bloc à copier et celle du bloc de destination. Elle est incrémenté ou décrémentée à chaque envoi de données sur le bus. Le compteur de ''byte'' restant est décrémenté à chaque copie d'un mot mémoire sur le bus. Le compteur de bytes restant est purement interne au contrôleur mémoire, alors que le contenu des compteurs d'adresse est envoyé sur le bus d'adresse à chaque copie de mot mémoire. [[File:DMA controller transfer count register.jpg|centre|vignette|upright=2.5|DMA controller transfer count register]] [[File:DMA controller memory address registers.jpg|centre|vignette|upright=2.5|DMA controller memory address registers]] Outre les compteurs, le contrôleur DMA contient aussi des registres de contrôle. Ils mémorisent des informations très variées : avec quel périphérique doit-on échanger des données, les données sont-elles copiées du périphérique vers la RAM ou l'inverse, et bien d’autres choses encore. [[File:Controleur DMA.png|centre|vignette|upright=2.5|Controleur DMA]] Le contrôleur DMA contient aussi des registres internes pour effectuer la copie de la source vers la destination. La copie se fait en effet en deux temps : d'abord on lit le mot mémoire à copier dans l'adresse source, puis on l'écrit dans l'adresse de destination. Entre les deux, le mot mémoire est mémorisé dans un registre temporaire, de même taille que la taille du bus. Mais sur certains bus, il existe un autre mode de transfert, où le contrôleur DMA ne sert pas d'intermédiaire. Le périphérique et la mémoire sont tous deux connectés directement au bus mémoire, la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. Les deux modes sont différents, le premier étant plus lent mais beaucoup plus simple à mettre en place. Il est aussi très facile à mettre en place quand le périphérique et la mémoire n'ont pas la même vitesse. ===Les modes de transfert DMA=== Il existe trois façons de transférer des données entre le périphérique et la mémoire : le mode ''block'', le mode ''cycle stealing'', et le mode transparent. Dans le '''mode block''', le contrôleur mémoire se réserve le bus mémoire, et effectue le transfert en une seule fois, sans interruption. Cela a un désavantage : le processeur ne peut pas accéder à la mémoire durant toute la durée du transfert entre le périphérique et la mémoire. Alors certes, ça va plus vite que si on devait utiliser le processeur comme intermédiaire, mais bloquer ainsi le processeur durant le transfert peut diminuer les performances. Dans ce mode, la durée du transfert est la plus faible possible. Il est très utilisé pour charger un programme du disque dur dans la mémoire, par exemple. Eh oui, quand vous démarrez un programme, c'est souvent un contrôleur DMA qui s'en charge ! Dans le '''mode cycle stealing''', on est un peu moins strict : cette fois-ci, le contrôleur ne bloque pas le processeur durant toute la durée du transfert. En cycle stealing, le contrôleur va simplement transférer un mot mémoire (un octet) à la fois, avant de rendre la main au processeur. Puis, le contrôleur récupérera l'accès au bus après un certain temps. En gros, le contrôleur transfère un mot mémoire, fait une pause d'une durée fixe, puis recommence, et ainsi de suite jusqu'à la fin du transfert. Et enfin, on trouve le '''mode transparent''', dans lequel le contrôleur DMA accède au bus mémoire uniquement quand le processeur ne l'utilise pas. ===Les limitations en termes d’adressage=== Les contrôleurs DMA sont parfois limités sur quelques points, ce qui a des répercussions dont il faut parler. Les limitations que nous voir ne sont pas systématiques, mais elles sont fréquentes, aussi il vaut mieux les connaitres. La première limitation est que le contrôleur DMA n'a pas accès à tout l'espace d'adressage du processeur. Par exemple, le contrôleur DMA du bus ISA, que nous étudierons plus bas, avait accès à seulement aux 16 mébioctets au bas de l'espace d'adressage, à savoir les premiers 16 mébioctets qui commencent à l'adresse zéro. Le processeur pouvait adresser bien plus de mémoire. La chose était courante sur les systèmes 32 bits, où les contrôleurs DMA géraient des adresses de 20, 24 bits. Le problème est systématique sur les processeurs 64 bits, où les contrôleurs DMA ne gèrent pas des adresses de 64 bits, mais de bien moins. Typiquement, le contrôleur DMA ne gère que les adresses basses de l'espace d'adressage. Le pilote de périphérique connait les limitations du contrôleur DMA, et prépare les blocs aux bons endroits, dans une section adressable par le contrôleur DMA. Le problème est que les applications qui veulent communiquer avec des périphériques préparent le bloc de données à transférer à des adresses hautes, inaccessibles par le contrôleur DMA. La solution la plus simple consiste à réserver une zone de mémoire juste pour les transferts DMA avec un périphérique, dans les adresses basses accessibles au contrôleur DMA. La zone de mémoire est appelée un '''tampon DMA''' ou encore un ''bounce buffer''. Si une application veut faire un transfert DMA, elle copie les données à transmettre dans le tampon DMA et démarre le transfert DMA par l'intermédiaire du pilote de périphérique. Le transfert en sens inverse se fait de la même manière. Le périphérique copie les données transmises dans le tampon DMA, et son contenu est ensuite copié dans la mémoire de l'application demandeuse. Il peut y avoir des copies supplémentaires vu que le tampon DMA est souvent géré par le pilote de périphérique en espace noyau, alors que l'application est en espace utilisateur. Diverses optimisations visent à réduire le nombre de copies nécessaires, elles sont beaucoup utilisées par le code réseau des systèmes d'exploitation. ===Les limitations en termes d’alignement=== La seconde limitation des contrôleurs DMA est qu'ils ne gèrent que des transferts alignés, c'est-à-dire que les adresses de départ/fin du transfert doivent être des multiples de 4, 8, 16, etc. La raison est que le contrôleur DMA effectue les transferts par blocs de 4, 8, 16 octets. En théorie, les blocs pourraient être placés n'importe où en mémoire, mais il est préférable qu'ils soient alignés pour simplifier le travail du contrôleur DMA et économiser quelques circuits. Les registres qui mémorisent les adresses sont raccourcis, on utilise moins de fils pour le bus mémoire ou la sortie du contrôleur DMA, etc. À noter que cet alignement est l'équivalent pour le contrôleur DMA de l'alignement mémoire du processeur. Mais les deux sont différents : il est possible d'avoir un processeur avec un alignement mémoire de 4 octets, couplé à un contrôleur DMA qui gère des blocs alignés sur 16 octets. Un défaut de cet alignement est que les blocs à transférer via DMA doivent être placés à des adresses bien précises, ce qui n'est pas garanti. Si le pilote de périphérique est responsable des transferts DMA, alors rien de plus simple : il dispose de mécanismes logiciels pour allouer des blocs alignés correctement. Mais si le bloc est fourni par un logiciel (par exemple, un jeu vidéo qui veut copier une texture en mémoire vidéo), les choses sont tout autres. La solution la plus simple est de faire une copie du bloc incorrectement aligné vers un nouveau bloc correctement aligné. Mais les performances sont alors très faibles, surtout pour de grosses données. Une autre solution est de transférer les morceaux non-alignés sans DMA? et de copier le reste avec le DMA. ===Le 8237 et son usage dans les PC=== Le 8237 d'Intel était un contrôleur DMA (''Direct Memory Access'') présents dans les premiers PC. Il a d'abord été utilisé avec les processeurs 8086, puis avec le 8088. Le processeur 8086 était un processeur 16 bits, c'est-à-dire manipulait les données sur 16 bits, mais utilisait des adresses sur 20 bits (adressage segmenté, en utilisant deux registres 16 bits, dont un registre de segment décalé de 4 bits). Le processeur 8088 était identique au 8086 mais utilisait un bus de données 8 bits (les instructions accédant la mémoire ou l'interface d'entrée-sortie sur 16 bits prenant des cycles supplémentaires car exécutées en deux temps : octet de poids faible, puis octet de poids fort), il avait lui aussi un bus d'adresse sur 20 bits, ce qui était incompatible avec les capacités 16 bits du 8237. Cependant, cela n'a pas empêché d'utiliser le 8237 sur les premiers PC, mais avec quelques ruses. ====L'usage du 8237 sur les premiers IBM PC==== Pour utiliser un 8237 avec un adressage de 16 bits sur un bus d'adresse de 20 bits, il faut fournir les 4 bits manquants. Ils sont mémorisés dans un registre de 4 bits, placés dans un circuit 74LS670. Ce registre est configuré par le processeur, mais il n'est pas accessible au contrôleur DMA. Les architectures avec un bus de 24 bits utilisaient la même solution, sauf que le registre de 4 bits est devenu un registre de 8 bits. Cette solution ressemble pas mal à l'utilisation de la commutation de banque (''bank switching''), mais ce n'en est pas vu que le processeur n'est pas concerné et que seul le contrôleur DMA l'est. Le contrôleur DMA ne gère pas des banques proprement dit, car il n'est pas un processeur, mais quelque chose d'équivalent que nous appellerons "pseudo-banque" dans ce qui suit. Les registres de 4/8 bits ajoutés pour choisir la pseudo-banque sont appelés des registres de pseudo-banque DMA ou page DMA. Un défaut de cette solution est que le contrôleur DMA gère 16 pages de 64 Kibioctets, au lieu d'une seul de 1 Mébioctets. S'il démarre une copie, celle-ci ne peut pas passer d'une page à une autre. Par exemple, si on lui demande de copier 12 Kibioctets, tout se passe bien si les 12 Kb sont tout entier dans une page. Mais si jamais les 3 premiers Kb sont dans une page, et le reste dans la suivante, alors le contrôleur DMA ne pourra pas faire la copie. Dans un cas pareil, le compteur d'adresse est remis à 0 une fois qu'il atteint la fin de la page, et la copie reprend au tout début de la page de départ. Ce défaut est resté sur les générations de processeurs suivantes, avant que les faibles performances du 8237 ne forcent à mettre celui-ci au rebut. Le 8237 peut gérer 4 transferts DMA simultanés, 4 copies en même temps. On dit aussi qu'il dispose de 4 '''canaux DMA''', qui sont numérotés de 0 à 3. La présence de plusieurs canaux DMA fait que les registres de pseudo-banque de 4/8 bits doivent être dupliqués : il doit y en avoir un par canal DMA. Le 8237 avait quelques restrictions sur ces canaux. Notamment, seuls les canaux 0 et 1 pouvaient faire des transferts mémoire-mémoire, les autres ne pouvant faire que des transferts mémoire-périphérique. Le canal 0 était utilisé pour le rafraichissement mémoire, plus que pour des transferts de données. ====Les deux 8237 des bus ISA==== Le bus ISA utilise des 8237 pour gérer les transferts DMA. Les registres de pseudo-banques sont élargis pour adresser 16 mébioctets (page sur 8 bits), mais la limitation des pseudo-banques de 64 Kibioctets est encore présente. Les PC avec un bus ISA géraient 7 canaux DMA, qui étaient gérés par deux contrôleurs 8237 mis en cascade. La mise en cascade des deux 8237 se fait en réservant le canal 0 du 8237 maître pour gérer le 8237 esclave. Le canal 0 du maitre n'est plus systématiquement utilisé pour le rafraichissement mémoire, cela libère la possibilité de faire des transferts mémoire-mémoire. Voici l'utilisation normale des 7 canaux DMA : Premier 8237 (esclave) : * Rafraichissement mémoire ; * Hardware utilisateur, typiquement une carte son ; * Lecteur de disquette ; * Disque dur, port parallèle, autres ; Second 8237 (maitre) : * Utilisé pour mise en cascade ; * Disque dur (PS/2), hardware utilisateur ; * Hardware utilisateur ; * Hardware utilisateur. Le bus ISA est un bus de données de 16 bits, alors que le 8237 est un composant 8 bits, ce qui est censé poser un problème. Mais le bus ISA utilisait des transferts spéciaux, où le contrôleur DMA n'était pas utilisé comme intermédiaire. En temps normal, le contrôleur DMA lit le mot mémoire à copier et le mémorise dans un registre interne, puis adresse l'adresse de destination et connecte ce registre au bus de données. Mais avec le bus ISA, le périphérique est connecté directement au bus mémoire et ne passe pas par le contrôleur DMA : la copie est directe, le contrôleur DMA s'occupe juste du bus d'adresse et n'accède pas au bus de données lors des transferts. ===L'usage du DMA pour les transferts mémoire-mémoire=== Le DMA peut être utilisé pour faire des copies dans la mémoire, avec des transferts mémoire-mémoire. Les performances sont correctes tant que le contrôleur DMA est assez rapide, sans compter que cela libère le processeur du transfert. Le processeur peut faire autre chose en attendant, tant qu'il n'accède pas à la mémoire, par exemple en manipulant des données dans ses registres ou dans son cache. La seule limitation est que les blocs à copier ne doivent pas se recouvrir, sans quoi il peut y avoir des problèmes, mais ce n'est pas forcément du ressort du contrôleur DMA. Un exemple de ce type est celui du processeur CELL de la Playstation 2. Le processeur était en réalité un processeur multicœur, qui regroupait plusieurs processeurs sur une même puce. Il regroupait un processeur principal et plusieurs coprocesseurs auxiliaires, le premier étant appelé PPE et les autres SPE. Le processeur principal était un processeur PowerPC, les autres étaient des processeurs en virgule flottante au jeu d'instruction beaucoup plus limité (ils ne géraient que des calculs en virgule flottante). Le processeur principal avait accès à la mémoire RAM, mais les autres non. Les processeurs auxiliaires disposaient chacun d'un ''local store'' dédié, qui est une petite mémoire RAM qui remplace la mémoire cache, et ils ne pouvaient accéder qu'à ce ''local store'', rien d'autre. Les transferts entre PPE et SPE se faisaient via des transferts DMA, qui faisaient des copies de la mémoire RAM principale vers les ''local stores''. Chaque SPE incorporait son propre contrôleur DMA. [[File:Schema Cell.png|centre|vignette|upright=2|Schema du processeur Cell]] ===DMA et cohérence des caches CPU=== Les transferts DMA se font entre la RAM et un périphérique, et les deux sens de transfert sont possibles. Certains périphériques se contentent d'un seul sens de transfert. Par exemple, pour une carte son, les transferts se font de la RAM vers la carte son, l'autre sens se résume à des interruptions. Le transfert DMA envoie les données sonores vers la carte son qui se charge d'envoyer le tout sur les hauts-parleurs ou un casque. Mais d'autres périphériques font des transferts dans l'autre sens, du périphérique vers la RAM. Pensez à une carte réseau : les téléchargements impliquent que les données reçues par la carte réseau sont copiées en RAM, alors que c'est l'inverse pour l'''upload''. Et les transferts DMA dans le sens périphériques -> RAM posent un problème sur les architectures avec une mémoire cache. De tels transferts peuvent modifier des adresses qui sont mises en cache. Or, les changements dans la RAM ne sont pas automatiquement propagés au cache. Le cache contient alors une copie de la donnée obsolète, qui ne correspond plus au contenu écrit dans la RAM par le contrôleur DMA. [[File:Cache incoherence write.svg|centre|vignette|upright=2|Cohérence des caches avec DMA.]] Pour résoudre ce problème, la solution la plus simple interdit de charger dans le cache des données stockées dans les zones de la mémoire attribuées aux transferts DMA, qui peuvent être modifiées par des périphériques ou des contrôleurs DMA. Toute lecture ou écriture dans ces zones de mémoire ira donc directement dans la mémoire RAM, sans passer par la ou les mémoires caches. L'interdiction est assez facile à mettre en place, vu que le processeur est en charge de la mise en place du DMA. Mais sur les systèmes multicœurs ou multi-processeurs, les choses sont plus compliquées, comme on le verra dans quelques chapitres. Une autre solution est d'invalider le contenu des caches lors d'un transfert DMA. Par invalider, on veut dire que les caches sont remis à zéro, leurs données sont effacées. Cela force le processeur à aller récupérer les données valides en mémoire RAM. L'invalidation des caches est cependant assez couteuse, comme nous le verrons dans le chapitre sur les caches. Elle est plus performante si les accès à la zone de mémoire sont rares, ce qui permet de profiter du cache 90 à 99% du temps, en perdant en performance dans les accès restants. Différentes technologies matérielles ont été inventées pour éliminer le problème à la source. L'une d'entre elles était la technologie ''Data Direct I/O'' d'Intel. Elle permettait à un périphérique d'écrire directement dans le cache du processeur, sans passer par la mémoire. Si elle résout le problème de la cohérence des caches, son but premier était d'améliorer les performances des communications réseaux, lorsqu'une carte réseau veut écrire quelque chose en mémoire. Au lieu d'écrire le message réseau en mémoire, avant de le charger dans le cache, l'idée était de passer outre la RAM et d'écrire directement dans le cache. Cette technologie était activée par défaut sur les plateformes Intel Xeon processor E5 et Intel Xeon processor E7 v2. ===La cohérence des caches périphériques=== Un problème similaire au précédent se manifeste sur les périphériques qui incorporent une mémoire cache. Nous parlons ici de périphériques qui n'ont pas de mémoire RAM interne, et dont le cache contient des copies de données présentes en RAM système, en RAM principale. Nous appellerons de tels caches des '''caches périphériques''', sous entendu intégrés à un périphérique. Il est important de préciser que de tels caches peuvent maintenir des copies de données en RAM système. Les caches ne pouvant pas contenir de données provenant de la RAM système ne sont pas concernés par ce qui suit, ce sont des caches purement internes au périphérique. [[File:Cache périphérique.png|centre|vignette|upright=2|Cache périphérique]] Certaines cartes réseaux incorporent de tels caches, pour améliorer les performances lors des transferts réseaux. Le cache périphérique permet alors soit précharger les données à uploader depuis la RAM, soit en accumulant les données téléchargées avant de tout envoyer d'un seul bloc dans la RAM. Mais précisons que seules les cartes réseaux de haute performance, destinées aux serveurs et ''mainframes'', intègrent des caches périphériques. Les cartes réseaux des PC n'ont pas de cache périphériques de ce type, elles se contentent de mémoire tampon de type FIFO qui ne sont pas des caches. Avec de tels périphériques, il arrive que le processeur modifie des données en RAM, alors qu'une copie est dans le cache périphérique. Il faut alors que l'écriture en RAM soit propagée dans le cache périphérique. Et cela demande là encore d'utiliser des mécanismes de cohérence des caches. La solution la plus simple est d'invalider le cache périphérique avant un transfert DMA, ou avant toute écriture en mémoire RAM qui peut poser problème. Typiquement, c'est le pilote du périphérique qui s'en occupe. Il réserve quelques portions de RAM pour communiquer avec le périphérique, et toute écriture dedans entrainera une invalidation. Le périphérique doit gérer nativement des commandes d'invalidation, qui ordonnent au périphérique d’invalider son cache périphérique. Le pilote du périphérique envoie ces commandes quand la situation l'exige. La technologie CXL (''Compute Express Link'') permet d'aller plus loin, en ajoutant un protocole de cohérence des caches au PCI Express. Le protocole CXL est une surcouche au PCI Express, qui est gérée par le processeur. Plus précisément, CXL réutilise la couche physique du protocole, mais change les couches transport et liaison. Concrètement, cela signifie que les interconnexions série du PCI Express sont réutilisables telles quelles par CXL, ce qui est l'idéal niveau compatibilité matérielle. Elle réutilise des mécanismes de cohérence des caches utilisés sur les processeurs multicœurs, qui seront décrits dans un chapitre ultérieur (un protocole de type MESI, pour rentrer dans les détails). L'intérêt est que les caches ne sont pas invalidés à chaque écriture problématique, la donnée adéquate est simplement mise à jour. De plus, la mise à jour et la détection des écritures fautives ne fait pas intervenir le pilote de périphérique, tout est géré par le processeur lui-même. Mais CXL va au-delà de la simple cohérence des caches périphériques. Il permet aussi au processeur d'adresser de la mémoire RAM installée sur un périphérique, à travers une liaison PCI Express. Toujours est-il que CXL est un regroupement de trois protocoles distincts, nommés CXL.io, CXL.cache, et CXL.memory. CXL.io gère des transferts DMA, la mémoire virtuelle du périphérique, les interruptions, la découverte des périphériques CXL, leur configuration, la gestion des erreurs, etc. CXL.cache gère la cohérence des caches périphériques, il permet à un périphérique d’accéder et de cacher la RAM système. Enfin, CLX.mem permet au processeur d'adresser la mémoire RAM du périphérique. Il faut noter que chaque périphérique implémente CXL a sa sauce. Il doit au minimum implémenter CXL.io, mais fait ce qu'il veut pour CXL.cache et CXL.mem. Et cela permet de classer les périphériques CXL en trois types. * Le type 1 correspond aux périphériques sans RAM intégrée, avec un cache périphérique. Ils peuvent se passer de CXL.mem, mais pas de CXL.cache. * Le type 2 correspond aux GPU et autres cartes accélératrices, qui ont une RAM intégrée qui gagne à être adressée directement par le processeur. Dans ce cas, il faut implémenter CXL.io, CXL.cache, et CXL.memory. * Le type 3 correspond à des ''RAM drive'', des systèmes permettant d’ajouter de la RAM à l'ordinateur sans ajouter de RAM système. La RAM ajoutée est placée sur un périphérique PCI Express, mais sert de complément à la RAM principale. l'idée est que les données sont déplacées entre RAM système et RAM du périphérique selon les besoins, la RAM du périphérique servant de second ''swapfile'' de la mémoire virtuelle. La cohérence des caches n'est alors pas nécessaire, CXL.cache n'est pas utilsié, alors que CXL.mem l'est. ===L'interaction entre DMA et mémoire virtuelle : l'IO-MMU=== Le contrôleur DMA est initialisé par le processeur avec des adresses de départ. Mais que se passe-t-il si le processeur utilise la mémoire virtuelle, l'abstraction mémoire ? Dans ce cas, le processeur initialise le contrôleur DMA avec des adresses virtuelles, ce qui pose problème. Pour éviter cela, les contrôleurs DMA modernes incrémentent des adresses virtuelles nativement, mais les traduisent en adresse physique avant tout accès à la RAM. En clair, les contrôleurs DMA incorporent une '''IO-MMU''', un circuit qui traduit les adresses virtuelles en adresses physiques. Elle est similaire à la MMU du processeur, si ce n'est qu'elle est intégrée dans le contrôleur DMA. La IO-MMU est intégrée dans le contrôleur DMA, qui peut être dans le ''chipset'' de la carte mère ou dans un périphérique. Si elle est dans le ''chipset'', elle est partagée entre plusieurs périphériques. Mais il n'est pas rare que la IO-MMU soit intégré dans un périphérique. L'exemple type est celui des cartes graphiques AGP et PCI-Express, qui utilisaient une IO-MMU standardisée, appelée la ''Graphics address remapping table''. La première carte graphique NVIDIA, la NV1, utilisait un système similaire, comme illustré ci-dessous. [[File:Microarchitecture du GPU NV1 de NVIDIA.png|centre|vignette|upright=2|Microarchitecture de la carte graphique NV1 de NVIDIA]] Évidemment, la IO-MMU a sa propre table des pages, qui est gérée par le pilote de périphérique, qu'on nommera '''table des pages IO'''. La IO-MMU intègre aussi des ''page table walkers'', des TLBs et toutes les optimisations associées. La table des pages de l'IO-MMU est séparée des autres tables des pages, bien que certaines optimisations permettent de fusionner les deux. Placer la table des pages IO en mémoire RAM fonctionne très bien pour les contrôleurs DMA intégré au ''chipset'' de la carte mère, voire ceux dans le processeur lui-même. Mais les périphériques qui incorporent un contrôleur DMA font autrement. Avec une table des pages IO en RAM, le périphérique devrait y accéder à travers le bus. Par exemple, une carte graphique devrait passer par le bus PCI-Express à chaque accès à la table des pages IO. Les performances seraient catastrophiques, même si on peut limiter la casse avec une TLB de grande taille. En pratique, le périphérique incorpore de la mémoire RAM et met la table des pages IO dedans. Par exemple, les cartes graphiques incorporent une mémoire vidéo de grande taille, dont elles peuvent réserver une petite partie pour la table des pages. La carte graphique NV1 de NVIDIA faisait autrement : elle mettait la table des pages dans une mémoire dédiée. [[File:IO-MMU et table des pages sur périphérique avec RAM locale.png|centre|vignette|upright=2|IO-MMU et table des pages sur périphérique avec RAM locale]] L'usage de l'IO-MMU permet de passer outre les limitations d'adressage du contrôleur DMA. Le contrôleur DMA peut gérer des adresses virtuelles très courtes, qui sont traduites en adresses physiques longues. Il s'agit d'un cas particulier d'extension d'espace d'adressage, qui porte le nom de '''''DMA Remapping'''''. Le résultat est que l'on peut se passer de tampons DMA, des ''bounce buffer''. Les données à envoyer au périphérique peuvent être placées n'importe où en mémoire physique, le contrôleur DMA utilisera des adresses physiques assez longues pour les adresser. Un autre avantage est que l'on peut communiquer avec un périphérique DMA sans avoir à allouer des blocs de mémoire contiguë. Avec la pagination, un bloc de mémoire transféré via DMA peut être éclaté sur plusieurs pages séparées et dispersées en mémoire RAM. Le bloc de mémoire est contigu dans l'espace d'adressage, mais éclaté en mémoire physique. Un autre avantage est la protection mémoire. Avec une IO-MMU adéquate, les échanges entre processeur et périphérique sont sécurisés. La mémoire virtuelle des périphériques incorpore des mécanismes de protection mémoire. Grâce à eux, le périphérique ne peut pas accéder à des régions de la mémoire auquel le pilote de périphérique ne lui a pas donné accès. Une erreur de configuration ou de programmation ne permet pas au périphérique d'accéder à des régions mémoire protégées, qui ne lui sont pas allouées. Des tentatives de hacking basée sur des attaques DMA ne marchent plus avec ce type de mémoire virtuelle. Un dernier avantage de l'IO-MMU se manifeste sur les périphériques qui incorporent de la mémoire RAM. Un bon exemple est celui des cartes graphiques dédiées, qui incorporent actuellement plusieurs gibi-octets de mémoire vidéo. Pour éviter toute confusion, nous allons parler de '''mémoire locale''' pour la RAM dans le périphérique, de '''mémoire système''' pour la RAM de l'ordinateur. L'IO-MMU permet d'adresser plus de RAM qu'il n'y a de RAM locale, le surplus d'adresse étant pris dans la RAM système. C'est un peu l'équivalent de la mémoire virtuelle, sauf qu'au lieu d'utiliser un fichier ''pagefile'' sur le disque dur, on utilise de la RAM système. Par exemple, si on prend une carte graphique avec 6 gigas de RAM dédiée, elle pourra gérer jusqu'à 8 gigas de RAM : les 6 en mémoire vidéo, plus 2 gigas fictifs en mémoire système. [[File:Mémoire virtuelle des cartes graphiques dédiées.png|centre|vignette|upright=2|Mémoire virtuelle des cartes graphiques dédiées]] ==Le ''chipset'' de la carte mère== Pour résumer ce chapitre, la communication avec les périphériques demande beaucoup de composants matériels. Un ordinateur moderne contient plusieurs dizaines, si ce n'est des centaines, de contrôleurs de périphériques. Par exemple, un ordinateur de type PC contient des contrôleurs pour le bus USB, un pour la carte son, un autre pour le disque dur, un autre pour les péripéhriques PCI Express, et ainsi de suite. Et il faut aussi rajouter un contrôleur d'interruption, éventuellement un contrôleur DMA, pour que les performances soient dignes de ce nom. Auparavant, le contrôleur d'interruption et le contrôleur DMA étaient des circuits intégrés séparés, soudés sur la carte mère. Mais ce n'est plus le cas de nos jours. ===Les ''chipsets'' sont des regroupements de circuits très divers=== De nos jours, tout est intégré dans un circuit unique, appelé le ''chipset'' de la carte mère. Il intègre un contrôleur DMA, le contrôleur d'interruption, ainsi que de nombreux contrôleurs de périphériques. Par exemple, les contrôleurs USB sont directement intégrés dans le ''chipset'', idem pour les contrôleurs S-ATA/nVME et PCI Express. [[File:Chipset et contrôleurs de périphériques.png|centre|vignette|upright=2|Chipset et contrôleurs de périphériques]] Dire ce que regroupe un ''chipset'' est donc compliqué, car le contenu du ''chipset'' dépend fortement de la carte mère. Le résumer en un simple regroupement de circuits aux fonctions disparates est de loin la meilleure définition possible. Comme le disent si bien les anglais : "A chipset is a set of chips". Le regroupement de tout ces circuits dans un ''chipset'' est une conséquence de la miniaturisation des transistors. Souder une dizaine de contrôleurs de périphériques différents sur la carte mère, ce qui avait un coût en termes de branchements, de coût de production et autres. Mais la miniaturisation a permis de regrouper le tout dans un seul boîtier. Des composants autrefois soudés sur la carte mère, comme des ''timers'', la CMOS RAM ou la ''Real Time Clock'' peuvent parfois être intégrés aux ''chipset''. Et il intègre souvent le microcontrôleur de surveillance matérielle, qui surveille les températures et tensions, pour contrôler les ventilateurs et les régulateurs de voltage. Il faut aussi noter que les ''chipsets'' modernes servent aussi de répartiteurs, qui connectent entre eux CPU, RAM, IO et périphériques. Mais nous ne reparlerons pas de cela ici, c'est quelque chose qui est plus pour le prochain chapitre. Et nous en avions déjà parlé longuement dans le chapitre sur l'architecture de base d'un ordinateur. [[File:IO mappées en mémoire avec séparation des bus.png|centre|vignette|upright=2|IO mappées en mémoire avec séparation des bus, usage d'un répartiteur]] ===Un exemple de ''chipset'' pour le bus ISA=== Pour donner un exemple de ''chipset'' de ce genre, nous allons regarder du côté des anciens PC. Les premiers PC utilisaient un bus système unique, sur lequel tout était connecté. Le bus en question s'appelait le '''bus ISA''', pour ''Industry Standard Architecture''. De nombreux composants présents sur la carte mère étaient connectés sur ce bus ISA. La liste suivante, non-exhaustive, contient les composants principaux : * un contrôleur mémoire pour DRAM ; * le contrôleur de bus 8288 ; * un contrôleur d'interruptions 8259 et un contrôleur DMA 8237 * un contrôleur d'interface parallèle 8255 ; * un générateur d'horloge 8284 ; * une ''Real Time Clock'' pour gérer la date et l'heure ; * le ''Programmable Interval Timer'' (le fameux 8254 qu'on a vu dans le chapitre sur les ''timers'') ; * le contrôleur de clavier XT ; * l'Intel 82091AA, qui était connecté au lecteur de disquette, au port série et au port parallèle (deux connecteurs présents à l'arrière des vieux PC). [[File:Architecture de l'IBM PC compatible.png|centre|vignette|upright=2.5|Architecture de l'IBM PC compatible]] Un des premier ''chipset'' inventé pour les PC a été le 82C100. Il était prévu pour fonctionner avec un processeur Intel x86, un 286 pour être précis. Il combine le contrôleur de clavier avec les composants de la liste précédentes, à l'exception de la ''Real Time Clock''. Son successeur est le 82C206 ''Integrated Peripheral Controller'' (IPC). Les différences avec le précédent sont mineures. La ''Real Time Clock'' et la CMOS RAM sont maintenant intégré dans le ''chipset''. Le contrôleur DMA et le contrôleur d'interruption sont doublés : il y en a deux exemplaires. Par contre, le contrôleurs de clavier et d'interface parallèle 8255 disparaissent. Les deux ''chipsets'' ont été développés par l'entreprise Chips and Technologies. Ils étaient composés de quatre à cinq circuits intégrés distincts, donc 4 boitiers. Sur l'image suivante, vous pouvez voir les quatre ''chips'' sur la carte mère. Ils sont reconnaissables par leur forme carrée, et surtout par le logo de l'entreprise avec un "Chips" assez reconnaissable. [[File:Chicony CH-286N-16.jpg|centre|vignette|upright=2|Carte mère avec un ''chipset'' 82C100.]] ==Les coprocesseurs d'entrée-sorties== Pour finir, nous allons voir un dernier composant qui permet de grandement faciliter la communication avec les entrées-sorties. Il s'agit des '''coprocesseurs d'entrée-sorties''', qui déchargent le processeur principal de tout ce qui a trait aux entrées-sorties. Ils é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 I/O'''. [[File:Asmp 2.gif|centre|vignette|upright=2|Co-processeur pour l'accès aux entrées-sorties.]] Pour simplifier, les coprocesseurs I/O sont des '''contrôleurs DMA programmables'''. Les plus simples sont simplement des contrôleurs DMA émulés avec un processeur, mais la majorité est capables d'enchainer plusieurs transferts DMA à la suite l'un de l'autre. Et certains d'entre eux savaient se débrouiller avec la mémoire virtuelle, avec des capacités de traduction d'adresse virtuelles en adresses physiques. Ils sont apparus sur les anciens ''mainframes'', notamment sur les ''mainframes'' IBM. À l'époque, communiquer avec des périphérique demandait de faire des transferts DMA, mais les contrôleurs DMA n'existaient pas. Alors les contrôleurs DMA étaient émulés avec des processeurs dédiés, qui s'occupaient des transferts DMA, au jeu d'instruction spécialisé pour gérer des entrées-sorties. Et vu que c'était des processeurs, ils étaient programmables, dans une certaine mesure. C'était l'époque des ''channel I/O'' des vieux ''mainframes''. Pour les ordinateurs personnels, il a existé un coprocesseur I/O vendu par Intel : l'Intel 8089. Mais l'IBM PC originel ne l'ayant pas utilisé, il n'a pas été repris sur les PC suivants, ce qui l'a fait tomber dans l'oubli. les consoles de jeu utilisent souvent des coprocesseurs I/O. Pour être précis, elles utilisent un processeur normal comme coprocesseur d'entrée-sortie. Un exemple est celui de la console de jeu Nintendo 3DS, qui disposait d'un CPU ARM9, d'un coprocesseur pour les divisions qu'on abordera plus bas, et d'un processeur ARM7. L'ARM 7 était utilisé comme coprocesseur d'I/O, ainsi que pour l'émulation de la console GBA. Les coprocesseurs I/O exécutent un programme qui agit sur les entrées-sorties, souvent appelé un '''programme I/O'''. Avec un coprocesseur I/O, le CPU initie l'exécution d'un programme I/O sur le coprocesseur I/O. Le coprocesseur IO exécute alors le programme I/O dans son coin, séparément du processeur. Quand le programme IO s'est terminé, le coprocesseur I/O envoie une interruption au CPU pour le prévenir qu'il a terminé. Idem en cas d'erreur lors de la transmission. ===Les interconnexions entre coprocesseur IO, CPU et entrée-sorties=== Un coprocesseur I/O peut être relié au reste de l'ordinateur de plusieurs manières. L'essentiel est que le coprocesseur IO soit relié à la mémoire RAM, pour prendre en charge les transferts DMA. Rappelons en effet qu'un coprocesseur I/O est une sorte de contrôleur DMA sous stéroïde, ce qui requiert qu'il soit relié à la fois aux entrées-sorties et à la mémoire RAM. Il y a donc deux solutions : soit il est connecté à un bus système, soit il est utilisé en tant que répartiteur. Utiliser un bus système a de nombreux avantages. Le câblage du système est très simple, l'implémentation du DMA sur le coprocesseur I/O est aisée. Par contre, le processeur et le coprocesseur I/O doivent se synchroniser, histoire de ne pas accéder au bus en même temps, en utilisant les mécanismes d'arbitrage du bus. Utiliser le coprocesseur I/O comme répartiteur fait que celui-ci sert d'intermédiaire entre les entrée-sorties et le reste de l'ordinateur. Il est relié à deux bus : un ''bus processeur'' qui le relie au processeur, et un ''bus IO'' qui le relie aux entrée-sorties. Il reçoit des commandes sur le bus processeur et peut prévenir le processeur quand il a terminé son travail, que ce soit via ''pooling'' ou via des interruptions. [[File:Connexion d'un coprocesseur IO au reste de l'ordinateur.png|centre|vignette|upright=2|Connexion d'un coprocesseur IO au reste de l'ordinateur]] Rappelons que le programme I/O doit bien être mis quelque part. Avec un bus système, il est placé dans la RAM système, partagée entre processeur et coprocesseur IO. Pour initialiser le coprocesseur I/O, le processeur a juste besoin de l fournissant un pointeur qui pointe vers le programme IO (l'adresse du programme). Le défaut à cela est que le CPU et le coprocesseur I/O se marchent sur les pieds pour accéder à la mémoire RAM. Il est possible de résoudre ce problème en dédiant une RAM au coprocesseur, pour le programme IO. Reste à connecter cette RAM dédiée au coprocesseur I/O. Pour cela, il est possible de la placer sur le bus IO, au même titre que n'importe quelle entrée-sortie. C'est la solution la plus économe, mais elle demande d'utiliser le coprocesseur I/O comme un répartiteur, un intermédiaire. Une autre serait de connecter la RAM sur un bus mémoire dédié. Elle a l'avantage de marcher avec un bus système ou une utilisation en répartiteur, mais elle demande que le coprocesseur IO ait des broches dédiées à la mémoire. Dans les deux cas, le programme I/O doit être copié dans cette mémoire séparée, ce qui n'est pas de la tarte. [[File:Coprocesseur IO avec RAM dédiée sur le bus d'IO.png|centre|vignette|upright=2|Coprocesseur IO avec RAM dédiée sur le bus d'IO]] Les ''Channel IO'' des anciens ''mainframes'' commandaient un bus IO séparé. Par contre, l'Intel 8089 pouvait fonctionner dans deux modes : soit avec un bus système, soit pour commander un bus IO. ===Le jeu d'instruction du coprocesseur IO=== En général, le programme I/O émule les transferts DMA. Pour cela, le programme IO copie les mots mémoire un par un avec une boucle. Le coprocesseur IO incorpore pour cela les registres d'un contrôleur DMA : des registres pour l'adresse de la source et de la destination, des registres pour des indices de boucle, des compteurs pour le nombre d'octets à copier, etc. Les registres d'adresse sont généralement incrémentés ou décrémentés automatiquement, 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 se concentre surtout sur les accès mémoire et les branchements pour les boucles. Pour ce qui est des calculs, il a juste le minimum : 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. Par contre, ils disposent d'instructions de manipulation de bit assez riches et complexes. En effet, les pilotes de périphériques font beaucoup de manipulations de bits, que ce soit pour changer des bits dans un registre de contrôle, extraire les bits pertinents d'un registre d'état, ou bien d'autres manipulations. Pour résumer, leur jeu d’instruction est centré sur : les accès mémoire, les boucles, la manipulation de bit. Il faut noter que le processeur a souvent plusieurs ports, avec un port par entrée-sortie dans le meilleur des cas. Et chaque port dispose de ses propres registres pointeurs/computer de boucle. Par exemple, l'Intel 8089 avait deux ports dédiés aux entrée-sorties, et deux bancs de registres dédiés. Rien de surprenant, il y a l'équivalent sur n'importe quel contrôleur DMA. Chaque canal DMA a ses propres registres. Les coprocesseurs I/O peuvent faire communiquer des composants ayant des largeurs de bus différentes. Par exemple, il est possible de faire communiquer une entrés-sortie 8 bits avec une autre de 16 bits, ou de faire des transferts entre une RAM 32 bits et une entrée-sortie de 16 bits. Pour cela, le coprocesseur I/O fait automatiquement la conversion. Par exemple, pour un transfert d'un composant 16 bits vers un autre de 8 bits, il lit 16 bits depuis la source et découpe le doublet en deux octets, qu'il envoie un par un. Inversement, pour un transfert d'un composant 8 bits vers un composant 16 bits, il lit deux octets à la suite et les combine en un seul doublet, envoyé en une fois. ===L'Intel 8089=== [[File:Intel 8089.svg|vignette|Intel 8089]] L'Intel 8089 est un coprocesseur Intel, prévu pour fonctionner en tandem avec un CPU 8086 d'Intel. Il contient deux canaux DMA programmables, chacun ayant ses propres registres, ses propres interruptions, etc. Par contre, le reste du processeur n'est présent qu'en un seul exemplaire. Ce qui veut dire que ce n'est pas un processeur double cœur, il n'y a qu'une seule unité de contrôle, et une seule unité de calcul. Niveau instruction, on retrouve les instructions classiques, avec quelques ajouts dédiés aux transferts DMA. Les calculs se limitent à l'addition, l'incrémentation, la décrémentation, et les ET/OU/NON bit à bit. Il y a aussi deux instructions pour mettre un bit à 0 ou 1, ainsi que des branchements pour tester la valeur d'un bit dans un registre ou une adresse. Mais les instructions intéressantes sont : * les instructions de copie mémoire-mémoire ; * une instruction pour configurer un transfert DMA ; * une instruction pour démarrer un transfert DMA à la prochaine instruction ; * une instruction pour terminer l'exécution du programme I/O ; * une instruction pour mettre la sortie d’interruption à 1. Le 8089 contient des registres de 20/21 bits et des registres de 16 bits. Les registres de 20/21 bits sont destinés à recevoir des adresses, alors que les registres de 16 bits sont des registres de données. Si je dis 20/21, c'est en raison de l'adressage employé par le 8086, qui utilisait un espace d'adressage séparé pour la mémoire et les entrée-sorties. Les registres du 8089 mémorisent des adresses de 20 bits, et ajoutent un bit IO pour faire la différence entre les deux espaces d'adressage. Les registres d'adresse sont au nombre de quatre et sont nommés GA, GB, GC et TP. Les registres de 16 bits sont eux aussi au nombre de quatre et sont nommés BC, IX, MC et CC. Le registre TP est le ''program counter'' associé à un canal DMA. Les deux canaux DMA exécutent chacun leur propre programme, ce qui fait qu'ils ont chacun leur propre ''program counter''. Mais pour le reste, les registres ne font pas la même chose selon qu'un transfert DMA est en cours ou non. Lors d'un transfert DMA, les registres sont utilisés comme suit : * Le registre GA est utilisé pour adresser la source, le registre GB est utilisé pour adresser la destination. * Le registre GC est facultatif : il pointe vers une table de 256 octets, utilisée pour faire de la traduction d'adresse. * Le registre BC compte le nombre d'octets qu'il reste à transférer. Le registre MC mémorise un masque, qui est utilisé pour appliquer une opération de masquage avant d'écrire l'octet dans la destination. * Le registre CC est utilisé pour configurer certains transferts DMA, notamment quand plusieurs transferts DMA sont enchainés automatiquement par le 8089. * Les autres registres ne sont pas utilisés. En dehors d'un transfert DMA, les registres sont utilisés pour stocker des données, à l'exception de TP et de CC. * Les trois registres d'adresse GA, GB et GC peuvent être utilisés comme registre de donnée, entre deux transferts DMA. Mais ils sont alors utilisés comme des registres de 16 bits, les 4 bits de poids fort étant remplis avec le bit de signe du nombre qu'ils contiennent. * Le registre BC n'a pas de prédisposition en dehors des transferts DMA. * IX est prédisposé à servir de registre d'indice, si le mode d'adressage le demande. * MC est prédisposé à gérer un masque, qui est utilisé avec les instructions JMCE and JMCNE. Initialiser le 8089 demande de lui fournir des informations, regroupées dans deux blocs de mémoire : un ''Channel Control Block'' et un ''Command Parameter Block''. Les deux sont placés en mémoire RAM, le 8089 y accède en lisant dans la mémoire RAM, et le processeur doit fournir l'adresse de ces deux blocs de configuration au 8089 quand il l'initialise. Je ne vais pas donner une explication complète de ces deux blocs, mais je vais quand même donner quelques détails. Le ''Channel Control Block'' contient, pour chaque canal DMA, un octet de configuration. L'octet de configuration contient plusieurs bits de configuration. En premier lieu, on trouve trois bits qui permettent de démarrer, terminer, suspendre ou reprendre l'exécution du programme I/O. En second lieu, on trouve deux bits pour activer/désactiver les interruptions sur chaque canal DMA. Enfin, on a un bit de priorité qui permet de donner la priorité à un canal DMA sur l'autre. Si un canal DMA a ce bit à 1 et l'autre a un bit à 0, celui qui a le bit à 1 a la priorité. Le ''Command Parameter Block'' contient l'adresse du programme I/O, en mémoire RAM. Elle est en réalité composée de deux adresses, parce que le 8089 et le 8086 utilisent la segmentation. Il contient aussi un ''program status word'', qui remplace le registre d'état. En effet, le 8089 n'a pas vraiment de registre d'état. À la place, il utilise un octet en RAM, qui n'est autre que le ''program status word''. Il met à jour le ''program status word'' en mémoire RAM dès que nécessaire. <noinclude> {{NavChapitre | book=Fonctionnement d'un ordinateur | prev=L'abstraction mémoire et la mémoire virtuelle | prevText=L'abstraction mémoire et la mémoire virtuelle | next=L'adressage des périphériques | nextText=L'adressage des périphériques }} </noinclude> q9a0jz6u5evltbtpkeetaygt7vyiti5 Philosophie/Thalès de Milet/Textes et traductions période Principat Empire Romain 0 78966 768561 768396 2026-06-25T07:10:26Z Alex Mtlr 103840 /* Paragraphe III. */ 768561 wikitext text/x-wiki {| border="0" cellpadding="0" width="100%" style="background: #f9f9f9" | colspan="3" height="25"|<div style="text-align: center;">[[Philosophie/Thalès de Milet|'''Thalès de Milet''']]</div> |- | width="33%"|'''[[Philosophie/Thalès de Milet/Textes_et_traductions_période_République_Romaine|Période République Romaine]]''' |- | width="33%"|'''[[Philosophie/Thalès de Milet/Textes_et_traductions_période_Grèce_Hellénistique|Période de la Grèce Hellénistique]]''' |- | width="33%"|'''[[Philosophie/Thalès de Milet/Textes_et_traductions_Ier_millénaire_AEC|Période de la Grèce Classique]]''' |} {{EnTravaux}} <span style="font-size:18pt;">Période du Principat de l’[[w:Empire_romain|''Empire'']] [[#Empire|<span id="Empire_back"><sup>'''I'''</sup></span>]] [[w:Rome_antique|''Romain'']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Rome_back|<sup>🔄</sup>]]</span> <p style="text-align: right;">(16 janvier [[w:27_av._J.-C.|-27]] <sup>[[w:Ier_siècle_av._J.-C.|⏳]]</sup>, nomination de [[w:Auguste|'''Caius Iulius Caesar Octavianus''']] aux titres d’[[w:Auguste_(titre)|''Augustus'']] et de [[w:Princeps_senatus|''Princeps'']] par le [[w:Sénat_romain|''Sénat romain'']] — fin du [[w:IIIe_siècle|III<sup>ème</sup> siècle {{Info|EC|de l’Ère Commune}}]], création du système [[w:Tétrarchie|''tétrarchique'']] [[#tétrarchie|<span id="tétrarchie_back"><sup>'''II'''</sup></span>]] par [[w:Dioclétien|'''Dioclétien''']] [[#Dioclétien|<span id="Dioclétien_back"><sup>'''III'''</sup></span>]] pour faire face aux [[w:Invasions_barbares#Première_période_:_les_mouvements_migratoires_germaniques_du_IIIe_siècle|''incursions barbares'']]) {{Boîte déroulante début|titre=NdA Empire|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Empire_back|<span id="Empire"><sup>I</sup></span>]] Du nom commun latin imperium [[wikt:en:imperium#Latin|(en)]], « 1. L’empire, l’État, le gouvernement impérial, le royaume, la domination. 2. Le droit ou le pouvoir de commander ou d’avoir le contrôle ; domination. 3. Commandement ou autorité absolue sur l’empire (ou un autre régime politique) ; souveraineté ; domination. 4. (militaire) Autorité militaire, commandement (d’une armée). 5. L’exercice de l’autorité, de la règle, de la loi, du contrôle, de la souveraineté. 6. Un commandement, un ordre, une direction, une injonction.) »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ du verbe imperō, « 1. (avec datif) Commander, donner des ordres à, imposer, exiger. 2. Gouverner. »;<br /><p style="margin: 0 2em; text-indent: 30px;">➥ du préfixe prépositionnal in-, « 1. Dans, à l’intérieur. 2. Contre; dans; sur; vers. 3. (utiliser comme un intensifieur). 4. Attaché à des [[w:Aspect_inchoatif|''verbes inchoatifs'']], il peut exprimer le sens d’un changement en cours ou d’un achèvement partiel. »;<br /><p style="margin: 0 2em; text-indent: 30px;">➥ +‎ du verbe parō [[wikt:en:paro#Latin|(en)]], « 1. Arranger, ordonner, concevoir. 2. Fournir, meubler, préparer. 3. Résoudre, viser, décider. 4. Obtenir, acquérir, se procurer, se faire. »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ + du suffixe nominal abstractif‎ -ium [[wikt:en:-ium#Latin|(en)]], désignant parfois des offices et des groupes. »;<br /><p style="margin: 0 2em; text-indent: 15px;">L’historien spécialiste de l’[[w:Grèce_antique|''antiquité grecque'']] [[w:Moses_Finley|Moses Finley]] définit un empire par tout {{Info|''« exercice durable par un État d’une autorité, d’un pouvoir, ou d’un contrôle sur un ou plusieurs États, communautés ou peuples »''|Moses Finley, Économie et société en Grèce antique, La Découverte, 2007.}}. L’historien [[w:Jean_Tulard|Jean Tulard]], précise cette définition par {{Info|''cinq traits suivants''|Jean Tulard, Les Empires occidentaux de Rome à Berlin, PUF, 1997.}} :<br /><p style="margin: 0 2em; text-indent: 15px;"> • Une volonté expansionniste ;<br /><p style="margin: 0 2em; text-indent: 15px;">• Une organisation centralisée ;<br /><p style="margin: 0 2em; text-indent: 15px;">• Des peuples encadrés par une armature politique et fiscale commune ;<br /><p style="margin: 0 2em; text-indent: 15px;">• La croyance en une supériorité d’essence ;<br /><p style="margin: 0 2em; text-indent: 15px;">• Un début et une fin clairement identifiés. <br /><br /><p style="margin: 0 2em; text-indent: 15px;">'''[[#tétrarchie_back|<span id="tétrarchie"><sup>II</sup></span>]] Du nom commun grec ancien τετραρχία / tetrarkhía;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ du préfixe τετρα- / tétra- [[wikt:en:τετρα-#Ancient_Greek|(en)]], « quatre »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ + du suffixe -αρχία / -arkhía [[wikt:en:-αρχία#Ancient_Greek|(en)]], « -archie (forme de gouvernement ou de règle) »;<br /><p style="margin: 0 2em; text-indent: 15px;"> Système de gouvernement de l’Empire ''romain'' mis en place par Dioclétien à la fin du [[w:IIIe_siècle|III<sup>ème</sup> siècle {{Info|EC|de l’Ère Commune}}]], pour faire face aux invasions barbares. Il consiste en la division de la direction de l’empire entre, d’une part deux [[w:Empereur_romain|''empereurs'']] — les [[w:Auguste_(titre)|''augustes'']] —, d’autre part deux ''lieutenants'' (successeurs désignés des ''augustes'') — les [[w:C%C3%A9sar_(titre)|''césars'']]. <br /><br /><p style="margin: 0 2em; text-indent: 15px;">'''[[#Dioclétien_back|<span id="Dioclétien"><sup>III</sup></span>]] [[w:Nom_romain|Tria Nomina]] en latin {{Info|Gaius|praenomen, nom individuel du citoyen romain}} {{Info|Aurelius|nomen, nom de famille}} {{Info|Valerius|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}}, surnommé Dioclētiānus [[wikt:en:Diocletian#English|(en)]] lorsqu’il a été proclamé empereur par ses troupes;<br /><p style="margin: 0 2em; text-indent: 15px;"> Militaire et empereur, connu pour avoir séparé et élargi les services civils et militaires de l’empire, et réorganisé les divisions provinciales de l’empire, établissant le gouvernement le plus vaste et le plus bureaucratique de l’histoire de l’empire. En [[w:286|286]], il nomme son ''césar'', ou adjoint et successeur, [[w:Maximien_Hercule|Maximien]] ''Auguste'', co-empereur, et partage l’Empire entre l’Orient et l’Occident, puis en 293, y nomme respectivement [[w:Galère_(empereur_romain)|Maximien Galère]] et [[w:Constance_Chlore|Constance Chlore]] comme ''césar''.<br/><br/></div> ''' {{Boîte déroulante fin}} == [[w:Sénèque|'''Sénèque''']] [[#Sénèque|<span id="Sénèque_back"><sup>'''I'''</sup></span>]] == <p style="text-align: right;">([[w:5_av._J.-C.|-5]] <sup>[[w:Ier_siècle_av._J.-C.|⏳]]</sup> / [[w:1|1]] <sup>[[w:Ier_siècle|⏳]]</sup>, à [[w:Corduba|Corduba]] — 12 avril [[w:65|65]], à [[w:Rome_antique|''Rome'']], dans une maison de plaisance, la « quatrième pierre milliaire », contraint au [[w:Suicide_forcé|''suicide forcé'']] par l’empereur [[w:Néron|'''Néron''']] après avoir été dénoncé dans la [[w:Conjuration_de_Pison|''Conjuration de Pison'']], sans preuve selon [[w:Tacite|'''Tacite''']] [https://remacle.org/bloodwolf/historiens/tacite/annales15.htm <sup>Annales, l.V, §§LX-LXVI.</sup>]) [[s:Auteur:Sénèque_le_Jeune|<sup>📚</sup>]] [https://books.google.fr/books?id=_HZTvAxN7CIC&newbks=1&newbks_redir=0&lpg=PA3&dq=bibliogroup%3A%22Dictionnaire%20des%20philosophes%20antiques%22&hl=fr&pg=PA177#v=onepage&q&f=true {{Info|<sup>🔍</sup>|Dictionnaire des Philosophes Antiques, publié sous la direction de Richard Goulet, Volume VI, §43 - Seneca (Lucius Annaeus –)}}] [[Fichier:Duble_herma_of_Socrates_and_Seneca_Antikensammlung_Berlin_03_.jpg|vignette|<p style="text-align: justify; text-indent: 15px;">Double-hermès du [[w:IIIe_siècle|III<sup>ème</sup> siècle {{Info|EC|de l’Ère Commune}}]], unique portrait de '''Sénèque''' nommé et authentifié, et associé à celui de '''Socrate''', dont le point commun est celui d’avoir été contraint de se donner la mort. Copie ''romaine'' d’un modèle fait du vivant même du philosophe [https://books.google.fr/books?id=_HZTvAxN7CIC&newbks=1&newbks_redir=0&lpg=PA3&dq=bibliogroup%3A%22Dictionnaire%20des%20philosophes%20antiques%22&hl=fr&pg=PA180#v=onepage&q&f=true {{Info|<sup>➕</sup>|Dictionnaire des Philosophes Antiques, publié sous la direction de Richard Goulet, Volume VI, §43 - Seneca (Lucius Annaeus –), Iconographie}}] [https://books.google.fr/books?id=_HZTvAxN7CIC&newbks=1&newbks_redir=0&lpg=PA3&dq=bibliogroup%3A%22Dictionnaire%20des%20philosophes%20antiques%22&hl=fr&pg=PA201#v=onepage&q&f=true {{Info|<sup>➕➕</sup>|Dictionnaire des Philosophes Antiques, publié sous la direction de Richard Goulet, Volume VI, §43 - Seneca (Lucius Annaeus –), Iconographie - contribution de J. Lang}}].<br /><p style="text-align: justify; text-indent: 15px;">Matériau : Marbre blanc-brunâtre, légèrement veiné, finement cristallin.<br /><p style="text-indent: 15px;">Provenance : ''Rome'', 1813.<br /><p style="text-align: justify; text-indent: 15px;">Exposition : Staatliche Museen zu Berlin, Antikensammlung, SK. 391 [https://recherche.smb.museum/detail/698814/doppelherme-des-sokrates-und-seneca-mit-namensbeischriften-der-dargestellten <sup>🔍</sup>].]] <div style="text-align: justify; margin: 0 1em; text-indent: 15px">Homme politique romain, philosophe stoïcien et dramaturge, il devient tour à tour conseiller à la cour impériale sous '''Caligula''' puis '''Claude''', est exilé en 41 en ''Corse'', où il écrit ses premiers traités philosophiques avant d’être rappelé comme tuteur du jeune '''Néron''' en 49, et enfin, lorsque ce dernier accède au pouvoir, en devient le conseiller et l’un des personnages les plus influents de l’Empire.</div> {{Boîte déroulante début|titre=NdA Sénèque|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Sénèque_back|<span id="Sénèque"><sup>I</sup></span>]] [[w:Nom_romain|Tria Nomina]] en latin {{Info|Lucius|praenomen, nom individuel du citoyen romain}} {{Info|Annaeus|nomen, nom de famille}} {{Info|Seneca|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}}'''<br/><br/></div> {{Boîte déroulante fin}} === [[w:Sénèque#Physique|Questions naturelles]] === <p style="text-align: right;">[[s:Questions_naturelles|📚]] <div style="text-align: justify; margin: 0 1em; text-indent: 15px">Ouvrage de philosophie naturelle écrit vers 65. Il ne s’agit pas d’une [[w:Encyclopédie|encyclopédie]] [[#encyclopédie|<span id="encyclopédie_back"><sup>'''I'''</sup></span>]] systématique comme l’[[w:Histoire_naturelle_(Pline_l'Ancien)|''Histoire naturelle'']] [[#Histoire_naturelle|<sup>⤵️</sup>]] de [[w:Pline_l'Ancien|'''Pline l’Ancien''']] [[#Pline_l’Ancien_I|<sup>⤵️</sup>]], bien que ces 2 œuvres représentent les rares ouvrages romains consacrés à l’étude du monde naturel. L’investigation de '''Sénèque''' se déroule principalement à travers la prise en compte des points de vue d’autres penseurs, ''grecs'' et ''romains'', bien qu’elle ne soit pas dénuée de pensées originales, dont éthiques conforment à la pensée ''stoïcienne''.</div> {{Boîte déroulante début|titre=NdA Questions naturelles|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#encyclopédie_back|<span id="encyclopédie"><sup>I</sup></span>]] Du nom commun [[w:Latin#Latin_humaniste|latin Renaissance]] encyclopaedīa [[wikt:en:encyclopaedia#Latin|(en)]]; de l’expression grec ancien ἐγκύκλῐος παιδείᾱ / enkúklios paideíā [https://dumas.ccsd.cnrs.fr/dumas-03927443/file/DONNADILLE-MR2-Pline-VERS-FINALE.pdf {{Info|<sup>🔍</sup>|Lisa Donnadille. Merveilles animalières dans les livres VIII à XI de l’Histoire naturelle de Pline l’Ancien. Littératures. 2020. ffdumas-03927443, p.21}}], « cercle de l’éducation ou des sciences, l’ensemble des sciences qui constituent une éducation complète »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ de l’adjectif ἐγκύκλιος / enkúklios, littéralement « qui est rond ou tourne en rond, circulaire », ou au sens figuré « qui revient en cercle sur soi-même, périodique », « qui embrasse un cercle entier »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ du nom commun παιδεία / paideía, « l’éducation »;'''<br /><p style="margin: 0 2em; text-align: center;">« ''Si à première vue la signification de cette expression semble être sans ambiguïté, sa portée réelle et la compréhension qu’en avaient les auteurs grecs puis latins font l’objet de débats parmi les spécialistes. En effet, deux interprétations sont possibles lorsqu’un auteur de l’Antiquité a recours à cette expression dans l’un de ses textes. Dans le premier cas, cela équivaudrait à parler d’une éducation ordinaire, commune à tous ; et dans le second cas, cela ferait référence à la quantité de connaissances et de sciences qu’il faudrait maîtriser au préalable avant de commencer l’étude d’un sujet précis, qui serait dans ce cas placé en haut d’une hiérarchie dans les savoirs.'' »<br /><p style="margin: 0 2em; text-align: right;">''' Lisa Donnadille. [https://dumas.ccsd.cnrs.fr/dumas-03927443/file/DONNADILLE-MR2-Pline-VERS-FINALE.pdf Merveilles animalières dans les livres VIII à XI de l’Histoire naturelle de Pline l’Ancien. Littératures. 2020. ffdumas-03927443], p.21'''<br/><br/></div> {{Boîte déroulante fin}} ==== Livre III — De l’eau ==== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">En prologue, Sénèque explique pourquoi il est plus important de s’intéresser à l’observation du monde, à sa connaissance et à sa compréhension plutôt qu’à sa conquête. Puis, il développe diverses théories sur la formation des rivières, les eaux souterraines et les propriétés de l’eau. Dans une critique morale aux chapitres XVII à XIX, il fustige la mauvaise pratique consistant à amener à table des poissons, notamment des rougets, vivants et à se délecter de leurs couleurs changeantes à l’agonie avant de les préparer devant les convives. En épilogue, il énonce son [[w:Eschatologie|''eschatologie'']], sa vision de la fin du monde où les êtres vivants seront anéantis par des raz-de-marée, marquant la fin d’un cycle du vivant et le début d’un autre.</div> ===== <div style="text-align: center;">Chapitre XIII.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de la doctrine de '''Thalès''' faisant de l’eau l’élément à l’origine de la vie et critique d’une autre de la terre flottant dessus.</div> :'''Texte latin''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''13.''' Adiciam, ut '''Thales''' ait, «ualentissimum elementum est». Hoc fuisse primum putat, ex hoc surrexisse omnia. Sed nos quoque aut in eadem sententia, aut in uicinia eius sumus. Dicimus enim ignem esse qui occupet mundum et in se cuncta conuertat; hunc euanidum languentemque considere et nihil relinqui aliud in rerum natura igne restincto quam umoren; in hoc futuri mundi spem latere. Ita ignis exitus mundi est, umor primordium. Miraris ex hoc posse amnes semper exire qui pro omnibus fuit et ex quo sunt omnia? Hic umor in diductione rerum ad quartas redactus est, sic positus ut sufficere fluminibus edendis, ut riuis, ut fontibus posset.<br /><p style="text-indent: 15px;">'''14.''' Quae sequitur '''Thaletis''' inepta sententia est. Ait enim terrarum orbem aqua sustineri et uehi more nauigii mobilitateque eius fluctuare tunc cum dicitur tremere; non est ergo mirum si abundat umor ad flumina profundenda, cum in umore sit totus. Hanc ueterem et rudem sententiam explode. Nec est quod credas in hunc orbem aquam subire per rimas et facere sentinam.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-i-1979/page/n1/mode/2up <u>L. Annaei Senecae, Natvrales Qvaestiones</u>], [https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-i-1979/page/n261/mode/2up ''Liber Tertivs.''], [https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-i-1979/page/n293/mode/2up ''chap. 13.-14.''], texte établi par Carmen Codoñer Merino [[w:es:Carmen_Codoñer_Merino|(es)]], Consejo Superior de Investigaciones Científicas, Madrid, 1979</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''XIII.''' Aqua, ait '''Thales''', valentissimum elementum est : hoc fuisse primum putat, et hoc surrexisse omnia. Sed et nos quoque aut in eadem sententia, aut in ultima sumus. Dicimus enim ignem esse, qui occupet mundum, et in se cuncta convertat ; hunc evanidum considere, et nihil relinqui aliud in rerum natura, igne restincto, quam humorem : in hoc futuri mundi spem latere. Ita ignis exitus mundi est, humor primordium. Miraris amnes ex hoc posse exire semper, qui pro omnibus fuit, et ex quo sunt omnia? Hic humor in diductione rerum ad quartas redactus est, sic positus, ut fluminibus edendis sufficere, ut rivis, ut fontibus posset. Quæ sequitur, '''Thaletis''' inepta sententia est : ait enim , terrarum orbem aqua sustineri, et vehi more navigii, mobilitateque ejus fluctuare, tum quum dicitur tremere. Non est ergo mirum, si abundat humor ad flumina fundenda, quum mundus in humore sit totus. Hanc veterem et rudem sententiam explode : nec est quod credas, in hunc orbem aquam subire per rimas et facere sentiuam.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PP9#v=onepage&q&f=true <u>Œuvres Complètes de Sénèque, Tome Quatrième</u>], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA327#v=onepage&q&f=true ''Questions Naturelles, De Sénèque à Lucilius - Livre Troisième.''], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA342#v=onepage&q&f=true ''chap. XIII.''], traduction française de la collection Panckoucke, nouvelle édition très soigneusement revue par M. Charpentier et M. Félix Lemaistre, 1860</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''XIII.''' L’eau, dit '''Thalès''', est le plus puissant des éléments, le premier en date, celui par qui tout a pris vie. Nous pensons comme '''Thalès''', au moins sur le dernier point. En effet, nous prétendons que le feu doit s’emparer du monde entier et convertir tout en sa propre substance, puis s’évaporer, s’affaisser, s’éteindre et ne rien laisser autre chose dans la nature que l’eau ; qu’enfin l’eau recèle l’espoir du monde futur. Ainsi périra par le feu cette création dont l’eau fut le principe. Es-tu surpris que des fleuves sortent incessamment d’un élément qui a tenu lieu de tout, et duquel tout est sorti ? Quand les éléments furent séparés les uns des autres, l’eau fut réduite au quart de l’univers, et placée de manière à suffire à l’écoulement des fleuves, des ruisseaux, des fontaines. Mais voici une idée absurde de ce même '''Thalès'''. Il dit que la terre est soutenue par l’eau sur laquelle elle vogue comme un navire ; qu’à la mobilité d’un tel support sont dues les fluctuations qu’on appelle tremblements de terre. Ce ne sera donc pas merveille qu’il y ait assez d’eau pour entretenir les fleuves, si tout le globe est dans l’eau. Ce système grossier et suranné n’est que risible ; tu ne saurais admettre que l’eau pénètre notre globe par ses interstices, et que la cale est entr’ouverte.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[[s:Questions_naturelles_(trad._Baillard)|<u>Sénèque le Jeune</u>]], [[s:Questions_naturelles_(trad._Baillard)/Livre_3|''Livre III. chap. 13.'']], traduction par [[s:Auteur:Joseph_Baillard|Joseph Baillard]], Hachette, 1914<br />(également disponible [https://remacle.org/bloodwolf/philosophes/seneque/questionsnaturelles3.htm ici])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''XIII.''' L’eau, dit '''Thalès''', est le plus puissant des éléments : elle existait avant tout, elle est le principe de tout. Nous pensons comme '''Thalès''', au moins sur le dernier point. En effet, nous croyons que le feu, s’emparant du monde entier, convertira tout en sa propre substance : mais il finira par cesser ses ravages, et quand il sera éteint, dans toute la nature il ne restera que l’eau, et cette eau renfermera le germe et l’espérance d’un monde futur. Ainsi par le feu s’accomplira la destruction de l’univers, et par l’eau sa réorganisation. Êtes-vous surpris, maintenant, qu’après avoir tenu lieu de tous les éléments, et les avoir produits tous, l’eau suffise à l’entretien perpétuel des fleuves ? Quand les éléments furent séparés les uns des autres, l’eau fut réduite au quart de l’univers, et dans une proportion convenable pour suffire à l’alimentation des fontaines, des ruisseaux et des rivières. Mais voici une idée absurde du même '''Thalès''' : il dit que la terre est soutenue par l’eau, et qu’elle flotte sur elle comme un navire ; que les tremblements de terre sont causés par les oscillations et les mouvements du fluide qui la soutient. Il n’est donc pas étonnant qu’il y ait assez d’eau pour alimenter les fleuves, puisque tout le globe est dans l’eau. Mais rejetons cette vieille et informe hypothèse, qui assimile les sources aux flots que la cale entr’ouverte laisse pénétrer dans le vaisseau.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PP9#v=onepage&q&f=true <u>Œuvres Complètes de Sénèque, Tome Quatrième</u>], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA327#v=onepage&q&f=true ''Questions Naturelles, De Sénèque à Lucilius - Livre Troisième.''], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA342#v=onepage&q&f=true ''chap. XIII.''], traduction française de la collection Panckoucke, nouvelle édition très soigneusement revue par M. Charpentier et M. Félix Lemaistre, 1860</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''Chap. III.'''<br />''Opiniõ de Thales touchant l’eau.''<br /><p style="text-align: justify; text-indent: 15px;">L’eau , comme dit '''Thales''' , e[[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki>t</nowiki> le plus fort des Elemens. Il croit me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}me qu’elle e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t le premier , & que toutes cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es en ont pris nai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ance. Pour moy ie {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uis de cette opinion , ou du moins de la derniere partie de cette opinion. Car nous [[#nous_stoiciens_NdT_dR|<span id="nous_stoiciens_NdT_dR_back"><sup>1</sup></span>]] di{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ons que c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t le feu qui enueloppera tout le monde , & qui conuertira en {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oy toutes cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es; qu’il deuiendra {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ans force quand il n’aura plus de nourriture , qu’apres que le feu {{Info|ſ|forme ancienne longue de la lettre s minuscule}}era e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}teint il ne demeurera rien de re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te à la nature que l’eau {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement , & que c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t en elle {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eule que con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te l’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}perance d’vn monde futur. Ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i le feu e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t la fin du monde , & l’eau en e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t le commencement. Vous e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tonnez-vous donc que les fleuues pui{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ent tou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iours {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortir d’vn Element , qui e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t fait pour toutes cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es & dont toutes cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e font ? Lors que la nature fit le departement des Elemens , l’eau fut placée de telle {{Info|ſ|forme ancienne longue de la lettre s minuscule}}orte , qu’elle peut {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uffire pour les fleuues , pour les rui{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eaux , pour les fontaines. Mais ce que '''Thales''' dit en {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uitte e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t ridicule , car il dit que le Globe de la terre e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tenu par les eaux ; qu’elles le portent comme vn vai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eau , & qu’elles l’agitent de la me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}me {{Info|ſ|forme ancienne longue de la lettre s minuscule}}orte , lors que nous croyons qu’il tremble. Il ne faut donc pas s’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tonner , s’il ya tou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iours a{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ez d’eau pour former de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i grands fleuues , puis que tout le monde nage fur l’eau. Mais me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pri{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ez cette vieille , & cette gro{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}iere opinion , & ne croyez pas que l’eau vienne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur la terre , comme par des fentes & par des creua{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}es , & qu’elle y {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement comme dans le fond d’vn vai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eau.</div> <table cellspacing=15 style="margin: 0 4em; font-size:85%;"> <tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#nous_stoiciens_NdT_dR_back|<span id="nous_stoiciens_NdT_dR"><sup>1.</sup></span>]] Les Stoïciens.''' </td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=ktOcOg9lr54C&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii&hl=fr&pg=PA1#v=onepage&q&f=true <u>Seneque Des Qvestions Natvrelles</u>], [https://books.google.fr/books?id=ktOcOg9lr54C&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii&hl=fr&pg=PA202#v=onepage&q&f=true ''Livre Troisiesme. Des eaux.''], [https://books.google.fr/books?id=ktOcOg9lr54C&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii&hl=fr&pg=PA234#v=onepage&q&f=true ''chap. XIII.''], traduction par [[w:Pierre_Du_Ryer|Pierre Du Ryer]], A Lyon, Chez Christofle Fovrmy, 1663</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div> </div> <div style="text-align: center; margin: 0 auto;">― ● ―</div> ==== Livre IV — Du Nil ==== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">En prologue, '''Sénèque''' fait l’éloge de [[w:Lucilius_le_Jeune|'''Lucilius''']] [[#Lucilius|<span id="Lucilius_back"><sup>'''I'''</sup></span>]] avant de lui expliquer les dangers de la flatterie. Puis, il décrit la crue du [[w:Nil|''Nil'']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Nil_back|<sup>🔄</sup>]], expose les théories tentant de l’expliquer et les réfute. En épilogue, il fait le procès du luxe, et plus particulièrement celui d’acheter de la neige, et donc de marchandiser l’eau, regrettant qu’on ne puisse faire de même avec l’air et le soleil.</div> {{Boîte déroulante début|titre=NdA Lucilius|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Lucilius_back|<span id="Lucilius"><sup>I</sup></span>]] Du nom propre latin Lucilius [[wikt:en:Lucilius#Latin|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">Gouverneur ''romain'' de Sicile durant le règne de [[w:Néron|Néron]], ainsi qu’un ami et un correspondant de Sénèque.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:Ier_siècle_av._J.-C.|I<sup>er</sup> siècle {{Info|EC|de l’Ère Commune}}]], [[w:Campanie#Histoire|''Campanie'']], [[w:Quatorze_régions_de_la_Rome_augustéenne#Regio_I_:_Porte_Capène|''Regio I'']])'''<br/><br/></div> {{Boîte déroulante fin}} ===== <div style="text-align: center;">Chapitre II.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Réfutation par '''Sénèque''' d’une théorie explicite de '''Thalès''' sur la crue du ''Nil'' (théorie identique mais supposément implicite rapportée par [[w:Hérodote|'''Hérodote''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Chapitre_XX|<sup>🔄</sup>]]).</div> :'''Texte latin''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''2.''' [...] Si '''Thaleti''' credis, etesiae descendenti ''Nilo'' resistunt et cursum eius acto contra ostia mari sustinent. Ita reuerberatus in se recurrit, nec crescit, sed exitu prohibitus resistit et quacumque mox potuit ui congestus erumpit. '''Euthymenes''' ''Massiliensis'' testimonium dicit: «Nauigaui, inquit, ''Atlanticum'' mare. Inde ''Nilus'' fluit, maior, quamdiu etesiae tempus obseruant; tunc enim eicitur mare instantibus uentis. Cum resederunt, et pelagus conquiescit minorque descendenti inde uis ''Nilo'' est. Ceterum dulcis mari sapor est et similes ''Niloticis'' beluae». Quare ergo, si ''Nilum'' etesiae prouocant, et ante illos incipit incrementum eius et post eos durat? Praeterea non fit maior quo illi flauere uehementius, nec remittitur incitaturque, prout illis impetus fuit; quod fieret, si illorum uiribus cresceret. Quid quod etesiae litus ''Aegyptium'' uerberant et contra illos ''Nilus'' descendit, inde uenturus unde illi, si origo ab illis esset? Praeterea ex mari purus et caeruleus efflueret, non, ut nunc, turbidus ueniret. Adde quod testimonium eius testium turba coarguitur. Tunc erat mendacio locus; cum ignota essent externa, licebat illis fabulas mittere. Nunc uero tota exteri maris ora mercatorum nauibus stringitur, quorum nemo narrat initium ''Nili'' aut mare saporis alterius: quae natura credi uetat, quia dulcissimum quodque et leuissimum sol trahit. Praeterea quare hieme non crescit? Et tunc potest uentis concitari mare, aliquanto quidem majoribus; nam etesiae temperati sunt. Quod si e mari ferretur ''Atlantico'', semel oppleret ''Aegyptum''. At nunc per gradus crescit.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-ii-1979/page/n1/mode/2up <u>L. Annaei Senecae, Natvrales Qvaestiones</u>], [https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-ii-1979/page/n7/mode/2up ''Liber Qvartvs A.''], [https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-ii-1979/page/n41/mode/2up ''chap. 2.''], texte établi par Carmen Codoñer Merino [[w:es:Carmen_Codoñer_Merino|(es)]], Consejo Superior de Investigaciones Científicas, Madrid, 1979</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''II.''' [...] Si '''Thaleti''' credis, Etesiæ descendenti ''Nilo'' resistunt, et cursus ejus acto contra ostia mari sustinent : ita reverberatus in se recurrit : nec crescit, sed exitu prohibitus resistit, et quacumque mox potuit, inconcessus erumpit.<br /><p style="text-align: justify; text-indent: 15px;">'''Euthymenes''' ''Massiliensis'' testimonium dicit : « Navigavi, inquit, ''Atlanticum'' mare. Inde ''Nilus'' fluit major, quamdiu Etesiæ tempus observant : tunc enim ejicitur mare instantibus ventis. Quum resederint, et pelagus conquiescit, minorque descendenti inde vis ''Nilo'' est. Ceterum dulcis maris sapor est, et similes ''Niloticis'' belluæ. » Quare ergo, si ''Nilum'' Etesiæ provocant, et ante illos incipit incrementum ejus, et post eos durat ? Præterea non fit major, quo illi flavere vehementius. Nec remittitur, incitaturque, prout illis impetus fuit : quod fieret, si illorum viribus cresceret. Quid, quod Etesiæ littus ''ægyptium'' verberant, et contra illos ''Nilus'' descendit, inde venturus, unde illi, si origo ab illis esset ? Præterea ex mari purus et cæruleus efflueret, non ut nunc turbidus venit. Adde, quod testimonium ejus testium turba coarguitur. Tunc erat mendacio locus, quum ignota essent externa. Licebat illis fabulas mittere. Nunc vero tota exteri maris ora mercatorum navibus stringitur : quorum nemo narrat nunc cæruleum ''Nilum'', aut mare saporis alterius ; quod et natura credi vetat, quia dulcissimum quodque et levissimum sol trahit. Præterea quare hieme non crescit ? et tunc potest ventis concitari mare, aliquando quidem majoribus ; nam Etesiæ temperati sunt. Quod si e mari ferretur ''Atlantico'', semel oppleret ''Ægyptum''. At nunc per gradus crescit.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PP9#v=onepage&q&f=true <u>Œuvres Complètes de Sénèque, Tome Quatrième</u>], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA371#v=onepage&q&f=true ''Questions Naturelles, De Sénèque à Lucilius - Livre Quatrième.''], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA385#v=onepage&q&f=true ''chap. II.''], traduction française de la collection Panckoucke, nouvelle édition très soigneusement revue par M. Charpentier et M. Félix Lemaistre, 1860</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''II.''' [...] À en croire '''Thalès''', les vents étésiens repoussent le ''Nil'' à sa descente dans la mer, et suspendent son cours en poussant la mer contre ses embouchures. Ainsi refoulé, il revient sur lui-même, sans pour cela grossir ; mais l’issue lui étant barrée, il s’arrête, et bientôt, partout où il le peut, force le passage qui lui est refusé. [[w:Euthymènes|'''Euthymène''']] [[#Euthymènes|<span id="Euthymènes_back"><sup>'''I'''</sup></span>]], de ''Marseille'', en parle comme témoin : « J’ai navigué, dit-il, sur la mer ''Atlantique''. Elle cause le débordement du ''Nil'', tant que les vents étésiens se soutiennent ; car c’est leur souffle qui alors pousse cette mer hors de son lit. Dès qu’ils tombent, la mer aussi redevient calme, et le ''Nil'' à sa descente déploie moins de puissance. Du reste, l’eau de cette mer est douce, et nourrit des animaux semblables à ceux du ''Nil''. » Mais pourquoi, si les vents étésiens font gonfler le ''Nil'', la crue commence-t-elle avant la saison de ces vents, et dure-t-elle encore après ? D’ailleurs le fleuve ne grossit pas à mesure qu’ils soufflent plus violemment. Son plus ou moins de fougue n’est point réglé sur celle des vents étésiens, ce qui aurait lieu, si leur action le faisait hausser. Et puis ils battent la côte ''égyptienne'', le ''Nil'' descend à leur encontre : il faudrait qu’il vînt du même point qu’eux, si son accroissement était leur ouvrage. De plus, il sortirait pur et azuré de la mer, et non pas trouble comme il est. Ajoute que le témoignage d’'''Euthymène''' est réfuté par une foule d’autres. Le mensonge avait libre carrière, quand les plages étrangères étaient inconnues ; on pouvait de là nous envoyer des fables, À présent, la mer extérieure est côtoyée sur tous ses bords par des trafiquants dont pas un ne raconte qu’aujourd’hui le ''Nil'' soit azuré ou que l’eau de la mer soit douce. La nature elle-même repousse cette idée ; car les parties les plus douces et les plus légères sont pompées par le soleil. Et encore pourquoi le ''Nil'' ne croît-il pas en hiver ? Alors aussi la mer peut être agitée par des vents quelque peu plus forts que les étésiens, qui sont modérés. Si le mouvement venait de l’Atlantique, il couvrirait tout d’un coup l’Égypte : or l’inondation est graduelle.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[[s:Questions_naturelles_(trad._Baillard)|<u>Sénèque le Jeune</u>]], [[s:Questions_naturelles_(trad._Baillard)/Livre_4|''Livre IV.'']] ''chap. 2.'', traduction par [[s:Auteur:Joseph_Baillard|Joseph Baillard]], Hachette, 1914<br />(également disponible [https://remacle.org/bloodwolf/philosophes/seneque/questionsnaturelles4.htm ici])</div> {{Boîte déroulante début|titre=NdA trad. par Joseph Baillard de 1914|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Euthymènes_back|<span id="Euthymènes"><sup>I</sup></span>]] Du nom propre grec ancien Εὐθυμένης / Euthuménēs [[wikt:en:Εὐθυμένης#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;"> ➥ de l’adjectif εὐθύς / euthús, « 1. Droit, direct : (au sens moral) direct, ouvert, franc. »;<br /><p style="margin: 0 2em; text-indent: 15px;"> ➥ + du nom commun μενος / ménos, « 1. Esprit. 2. Désir, ardeur, souhait, but. 3. Colère. 4. Courage, esprit, vigueur. 5. Pouvoir, force. 6. Violence. »;<br /><p style="margin: 0 2em; text-indent: 15px;"> ➥ + du suffixe nominal‎ propre -ης / -ēs [[wikt:en:-ης#Suffix_2|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;"> Navigateur et explorateur de la mer Extérieure le long des côtes africaines (actuelle Atlantique sud).<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:VIeme_siècle_av._J.-C.|VI<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]], [[w:Marseille_antique#Massalia,_une_cité_grecque|''Massalia'']], actuelle Marseille)'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''II.''' [...] Selon '''Thalès''', le phénomène a pour cause les vents Étésiens, qui s’opposent au cours du ''Nil'' et font rebrousser ses eaux en sens inverse du mouvement qui le porte vers la mer. Refoulés sur eux-mêmes, les flots refluent sans pour cela grossir ; mais l’issue leur étant fermée, ils s’arrêtent, et bientôt ils s’ouvrent partout où ils peuvent le passage qui leur est refusé.<br /><p style="text-align: justify; text-indent: 15px;">'''Euthymène''' de ''Marseille'' en parle comme témoin : « J’ai navigué, dit-il, sur la mer ''Atlantique''. Le ''Nil'' roule des eaux plus abondantes, tant que durent les vents Étésiens ; car alors ils refoulent la mer sur le fleuve. Dès qu’ils se sont abattus et que la mer est devenue calme, le ''Nil'', qui peut redescendre vers celle-ci, diminue. Au reste, les eaux de cette mer sont douces et contiennent des animaux semblables à ceux du ''Nil''. » Dans cette hypothèse, qui donne les vents Étésiens pour cause des crues du ''Nil'', qu’on me dise pourquoi ces crues précèdent les vents, persistent quand les vents ne sont plus, enfin n’augmentent plus d’intensité et de violence, et ne diminuent pas selon la violence et l’impétuosité du vent même ; c’est pourtant ce qui devrait arriver, si les vents déterminaient la hausse des eaux. De plus, les vents Étésiens battent directement la côte ''égyptienne'' : pourquoi donc le ''Nil'' descend-il contre le souffle de ces vents, tandis qu’il devrait couler dans la même direction, s’il leur devait ses débordements ? Enfin, pourquoi, au lieu d’être diaphanes et azurés, ces flots, qu’on fait venir de la mer, sont-ils chargés de limon ? Ajoutez qu’une foule de témoignages réfutent '''Euthymène'''. On pouvait mentir, quand les plages étrangères étaient inconnues : c’était alors le temps des fables ; mais aujourd’hui mille vaisseaux marchands côtoient la mer extérieure ; personne ne dit que le ''Nil'' ait des flots d’azur ; personne ne donne à la mer une saveur douce, que la nature refuse à ses eaux : car le soleil en pompe sans cesse la partie la plus douce et la plus légère ; ensuite pourquoi le ''Nil'' ne croîtrait-il point pendant l’hiver ? la mer alors peut être battue par les vents, par des vents plus violents que les Étésiens, qui sont modérés. Enfin, si le mouvement venait de l’Atlantique, l’Égypte entière serait inondée tout d’un coup : or, l’inondation est graduelle.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PP9#v=onepage&q&f=true <u>Œuvres Complètes de Sénèque, Tome Quatrième</u>], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA371#v=onepage&q&f=true ''Questions Naturelles, De Sénèque à Lucilius - Livre Quatrième.''], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA385#v=onepage&q&f=true ''chap. II.''], traduction française de la collection Panckoucke, nouvelle édition très soigneusement revue par M. Charpentier et M. Félix Lemaistre, 1860</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''Chap. II.'''<br />''En quelle fai[[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki>ó</nowiki> {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e fait l’accroi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ement du Nil.''<br /><p style="text-align: justify; text-indent: 15px;">[...] Si vous en croyez '''Thales''' , les vents Ethe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iens re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tent au ''Nil'' en de{{Info|ſ|forme ancienne longue de la lettre s minuscule}}cendant dans la mer; & arre{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tent {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on cours, en pou{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ant la mer contre {{Info|ſ|forme ancienne longue de la lettre s minuscule}}es {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ept emboucheures. Si bien qu’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es {{Info|ſ|forme ancienne longue de la lettre s minuscule}}tant repou{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}é de la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}orte il retourne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oy-me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}me , & ne croi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t pas comme l’on pen{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e , mais par ce qu’il trouue vn ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tacle qui l’empe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}che de pa{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}er outre , il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t contraint de s’arre{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ter , & ne pouuant plus pour{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uiure {{Info|ſ|forme ancienne longue de la lettre s minuscule}}a cour{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e , il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pand par où il peut {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e répandre. '''Euthimenes''' de ''Mar{{Info|ſ|forme ancienne longue de la lettre s minuscule}}eille'' en rend ce te{{Info|ſ|forme ancienne longue de la lettre s minuscule}}moignage. I’ay nauigé, dit il, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur la mer ''Atlantique'' , & c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t par elle que le ''Nil'' deuient plus grand, lors que les vents Ethe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iens {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oufflent ; car alors cette mer {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ort pour ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i dire d’elle-me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}me par la force & par la violence de ces vents. Mais lors qu’ils ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oufflent plus la mer demeure tranquille, & le Nil ne trouue plus rien qui l’empe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}che de de{{Info|ſ|forme ancienne longue de la lettre s minuscule}}cendre , Au re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te l’eau de la mer e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t douce en ce temps-là , & l’on y void des be{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tes {{Info|ſ|forme ancienne longue de la lettre s minuscule}}emblables à celles du Nil. Mais {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i les Ethe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iens {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont enfler le Nil, pourquoy {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on débordement commence il auant qu’ils {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oufflent & pourquoy dure - il encore lors qu’ils ont ce{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}é de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ouffler. Dauantage ils ne s’enfle pas plus que de cou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tume , quand ces vents {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oufflent auecque plus de violence qu’ils ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont ordinairement. Enfim il ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e hau{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}e & ne s’abai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}e pas {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon que leur impetuo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ité e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t plus ou moins grande , ce qui arriveroit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ans doute s’il s’enfloit par la force de ces véts. Mais comme les Ete{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iens battent directement les bords de l’Egypte, & que le Nil de{{Info|ſ|forme ancienne longue de la lettre s minuscule}}cend contre eux ; il faudroit s’ils e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}toient cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on accroi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ement , qu’il commença{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t par l’endroit d’où ils viennent. Outre cela il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortiroit tout pur de la mer, & de la couleur de la mer, & ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit pas trouble & limonneux , comme il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t. Et apres tout le te{{Info|ſ|forme ancienne longue de la lettre s minuscule}}moignage d’'''Euthimene''' , e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t condamné par le plus grand nombre. Il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}toit permis de mentir quand on n’auoit point de connoi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ance des pays e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}trangers ; & alors on pouuoit facilement nous en enuoyer des fables. Mais aujourd’huy tous les riuages des mers les plus e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}loignées {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont remplis de vai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eaux de marchands, & pas vn ne nous apporte que le ''Nil'' {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oit de la couleur de la mer, ou que la mer ait vn autre gou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t. Quand nous aurions des rai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ons pour nous la per{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uader , la nature nous empe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}cheroit de le croire par ce que le Soleil en attire ce qu’il y a de plus leger & de plus doux. Dauantage pourquoy ne croi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t-il pas en Hyuer , puis que la met en ce temps là peut e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tre agitée par des vents plus violents, que les Ethe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iens qui {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont tou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iours moderez. Que {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i le ''Nil'' venoit de la mer ''Atlantique'' , il couuriroit l’Egypte tout d’vn coup, & neantmoins il ne la couure que peu à peu.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=gEzVYlz3c3cC&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii%20volume%202&hl=fr&pg=PA1#v=onepage&q&f=true <u>Seneque Des Qvestions Natvrelles</u>], [https://books.google.fr/books?id=gEzVYlz3c3cC&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii%20volume%202&hl=fr&pg=PA3#v=onepage&q&f=true ''Livre Qvatriesme. De la Nege, de la Greſle, & de la Pluye.''], [https://books.google.fr/books?id=gEzVYlz3c3cC&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii%20volume%202&hl=fr&pg=PA37#v=onepage&q&f=true ''chap. II.''], traduction par [[w:Pierre_Du_Ryer|Pierre Du Ryer]], A Lyon, Chez Christofle Fovrmy, 1663</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div> </div> <div style="text-align: center; margin: 0 auto;">― ● ―</div> ==== Livre VI — Des tremblements de terre ==== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">En prologue, '''Sénèque''' énonce le plan du livre, des causes des tremblements de terre et des peurs qu’ils provoquent, en s’appuyant sur celui de ''Campanie'' qui a récemment causé d’importants dégâts à ''Pompéi'' et à ''Herculanum''. Des chapitres IV à XX, de nombreuses théories sismiques sont présentées, la plupart liées au nom d’un philosophe qui les prône. Le feu, l’eau et l’air sont cités comme causes, et plusieurs d’entre-elles les combinent. À partir du chapitre XXIV, '''Sénèque''' développe sa propre opinion : l’air pénétrant, qui remplit complètement les cavités souterraines sous une forte pression, en est la cause. En épilogue, il explique à '''Lucilius''' quel comportement adopté en de telles situations, à savoir être courageux en ne craignant pas la mort</div> ===== <div style="text-align: center;">Chapitre VI.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Réfutation d’une théorie de '''Thalès''' de la Terre flottant sur l’eau, et témoignage d’une de ses preuves portant sur les tremblements de terre.</div> :'''Texte latin''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''6.''' In aqua causam esse nec ab uno dictum est nec uno modo. '''Thales''' ''Milesius'' totam terram subiecto iudicat umore portari et innare, siue illud oceanum uocas, siue magnum mare, siue alterius naturae simplicem adhuc aquam et umidum elementum. Hac, inquit, unda sustinetur orbis uelut aliquod grande nauigium et graue his aquis quas premit. Superuacuum est reddere causas propter quas existimat grauissimam partem mundi non posse spiritu tam tenui fugacique gestari; non enim nunc de situ terrarum sed de motu agitur. Illud argumenti loco ponit aquas esse in causa quibus hic orbis agitetur, quod in omni maiore motu erumpunt fere noui fontes, sicut in nauigiis quoque euenit ut, si inclinata sunt et abierunt in latus, aquam sorbeant, quae in omni eorum onere quae uehit, si immodice depressa sunt, aut superfunditur aut certe dextra sinistraque solito magis surgit. Hanc opinionem falsam esse non est diu colligendum. Nam, si terra aqua sustineretur et ea aliquando concuteretur, semper moueretur, nec agitari illam miraremur sed manere; deinde tota concuteretur, non ex parte; numquam enim nauis dimidia iactatur. Nunc uero terrarum non uniuersarum sed ex parte motus est. Quomodo ergo fieri potest ut, quod totum uehitur, totum non agitetur, si eo quo uehitur agitatum est? — At quare aquae erumpunt? — Primum omnium saepe tremuit terra et nihil umoris noui fluxit. Deinde, si ex hac causa unda prorumperet, a lateribus terrae circumfunderetur, sicut in fluminibus ac mari uidemus incidere ut incrementum aquarum, quotiens nauigia desidunt, in lateribus maxime appareat. Ad ultimum non tam exigua fieret quam tu dicis eruptio nec uelut per rimam sentina subreperet, sed fieret ingens inundatio ut ex infinito liquore et ferente uniuersa.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-ii-1979/page/n1/mode/2up <u>L. Annaei Senecae, Natvrales Qvaestiones</u>], [https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-ii-1979/page/n135/mode/2up ''Liber Qvintvs - De terrae motv''], [https://archive.org/details/seneca.-cuestiones-naturales.-naturales-quaestiones.-vol.-ii-1979/page/n155/mode/2up ''chap. 6.''], texte établi par Carmen Codoñer Merino [[w:es:Carmen_Codoñer_Merino|(es)]], Consejo Superior de Investigaciones Científicas, Madrid, 1979</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''VI.''' In aqua causam esse, nec ab uno dictum est, nec uno modo. '''Thales''' ''Milesius'' totam terram subjecto judicat humore portari et innatare : sive illud Oceanum vocas, sive magnum mare, sive alterius naturæ simplicem adhuc aquam et humidum elementum. Hac, inquit, unda sustinetur orbis, velut aliquod grande navigium et grave his aquis, quas premit. Supervacuum est reddere causas, propter quas existimat, gravissimam partem mundi non posse spiritu tam tenui fugacique gestari ; non enim nunc de situ terrarum, sed de motu agitur. Illud argumenti loco ponit, aquas esse in causa, quibus hic orbis agitatur, quod in omui majore motu erumpunt fere novi fontes : sicut in navigiis quoque evenit, ut, si inclinata sunt et abiere in latus, aquam sorbeant, quæ in omni onere eorum quæ vehit, si immodice depressa sunt, aut superfunditur, aut certe dextra sinistraque solito magis surgit. Hanc opinionem falsam esse, non est diu colligendum ; nam, si terram aqua sustineret, et ea aliquando concuteretur : semper moveretur, nec agitari illam miraremur, sed manere. Tum tota concuteretur, non ex parte : nunquam enim navis dimidia jactatur. Nunc vero non terrarum universarum, sed ex parte motus est. Quomodo ergo fieri potest, ut quod totum vehitur, totum non agitetur, si eo quo vehitur, agitatum est ? At quare aquæ erumpunt ? Primum omnium sæpe tremuit terra, et nihil humoris novi fluxit. Deinde si ex hac causa unda prorumperet, a lateribus terræ circumfunderetur : sicut in fluminibus ac mari videmus accidere, ut incrementum aquarum, quoties navigia desidunt, in lateribus maxime appareat. Ad ultimum non tam exigua fieret quam dicit eruptio, nec velut per rimam sentina subreperet, sed fieret ingens inundatio, ut ex infinito liquore, et ferente universa.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PP9#v=onepage&q&f=true <u>Œuvres Complètes de Sénèque, Tome Quatrième</u>], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA420#v=onepage&q&f=true ''Questions Naturelles, De Sénèque à Lucilius - Livre Sixième.''], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA429#v=onepage&q&f=true ''chap. VI.''], traduction française de la collection Panckoucke, nouvelle édition très soigneusement revue par M. Charpentier et M. Félix Lemaistre, 1860</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''VI.''' Que l’eau soit cause des tremblements de terre, c’est ce qu’affirment divers auteurs et avec divers arguments. '''Thalès''' de ''Milet'' estime que le globe entier a pour support une masse d’eaux sur laquelle il flotte, et qu’on peut appeler Océan ou grande mer, ou élément jusqu’ici de nature simple, l’élément humide. Cette eau, dit-il, soutient la terre ; et l’immense navire pèse sur le liquide qu’il comprime. Il est superflu d’exposer les motifs qui font croire à '''Thalès''' que la partie de l’univers la plus pesante ne saurait porter sur une substance aussi ténue, aussi fugace que l’air : il ne s’agit pas maintenant de l’assiette du globe, mais de ses secousses. '''Thalès''' apporte en preuve de son système, que presque toujours les grandes secousses font jaillir des sources nouvelles, comme il arrive dans les navires qui, lorsqu’ils penchent et s’inclinent sur le flanc, sont envahis par l’eau ; toujours, s’il y a surcharge, l’eau vient couvrir le bâtiment, ou du moins s’élève à droite et à gauche plus que de coutume. La fausseté de cette opinion se démontre sans longs raisonnements. Si la terre était soutenue par l’eau, elle tremblerait quelquefois dans toute sa masse et toujours serait en mouvement ; ce ne serait pas son agitation qui étonnerait, mais son repos. Elle s’ébranlerait tout entière, non partiellement ; car ce n’est jamais la moitié seulement d’un navire qui est battue des flots. Or, les tremblements de notre terre ne sont pas universels, mais partiels. Comment serait-il possible qu’un corps porté tout entier par l’eau ne fût pas agité tout entier, quand ce fluide est agité ? « Mais d’où viennent les eaux qu’on a vues jaillir ? » D’abord, souvent la terre tremble, sans qu’il en sorte de nouvelles eaux. Ensuite, si telle était la cause de ces éruptions, elles n’auraient lieu qu’autour des flancs du globe ; ce que nous voyons arriver sur les fleuves et en mer : l’exhaussement de l’onde, à mesure que s’enfonce le navire, se remarque surtout aux flancs du bâtiment. Enfin l’éruption dont on parle ne serait pas si minime, et comme une voie d’eau qui s’infiltre par une fente légère ; l’inondation serait immense en raison de l’abîme infini sur lequel flotterait le monde.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[[s:Questions_naturelles_(trad._Baillard)|<u>Sénèque le Jeune</u>]], [[s:Questions_naturelles_(trad._Baillard)/Livre_6|''Livre VI.'']] ''chap. 6.'', traduction par [[s:Auteur:Joseph_Baillard|Joseph Baillard]], Hachette, 1914<br />(également disponible [https://remacle.org/bloodwolf/philosophes/seneque/questionsnaturelles6.htm ici])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''VI.''' Plusieurs philosophes ont prétendu que l’eau est la cause de ces secousses : ce que chacun explique à sa manière. '''Thalès''' de ''Milet'' prétend que le globe entier a pour support une masse d’eau sur laquelle il flotte ; peu importe qu’on donne à cet amas le nom d’Océan, de grande mer ou d’eau élémentaire, eau simple. Cette eau, dit-il, soutient la terre comme un grand vaisseau pesant sur le liquide qu’il comprime. Il est inutile d’exposer les raisons qui font croire à '''Thalès''' que le corps le plus pesant de la nature ne peut être soutenu par un fluide aussi délié et aussi rare que l’air : car il s’agit ici des tremblements de terre et non de l’assiette du globe. La grande raison de '''Thales''' pour faire de l’eau la cause des secousses de la terre, c’est que, dans tout tremblement considérable, jaillissent des eaux nouvelles ainsi les vaisseaux se remplissent d’eau quand ils penchent d’un côté ; chargés à l’excès, ou ils sont submergés, ou ils s’enfoncent à droite et à gauche plus profondément dans la mer. Il ne faut pas longtemps discuter pour voir la fausseté de cette opinion. Si la terre était soutenue par les eaux, elle serait quelquefois fortement ébranlée, mais de plus elle serait toujours flottante, et il faudrait s’étonner non de son agitation , mais de son repos ; enfin, au lieu d’être ébranlée en partie, elle le serait tout entière : car jamais la moitié d’un vaisseau n’est battue des flots. Or, on sait que les secousses de la terre sont partielles et non universelles : comment se ferait-il donc que ce qui est entièrement porté par les eaux ne fût pas entièrement agité, tandis que les eaux mêmes le sont en totalité ? Mais, dit-on, qui fait jaillir les eaux ? D'abord, souvent la terre tremble sans qu’il se produise des eaux nouvelles ; ensuite, si telle était la cause de ces éruptions, les eaux se répandraient latéralement autour de la terre. Ainsi, par exemple, quand un vaisseau s’enfonce ou dans la mer ou dans les fleuves, c’est vers les bords surtout que l’accroissement devient sensible. Enfin les sources qui jaillissent ne seraient pas si peu considérables ; on ne pourrait pas les comparer à une voie d’eau qui pénètre par les fentes du fond de cale : ce serait une inondation immense comme l’abîme infini sur lequel flotterait le monde.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PP9#v=onepage&q&f=true <u>Œuvres Complètes de Sénèque, Tome Quatrième</u>], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA420#v=onepage&q&f=true ''Questions Naturelles, De Sénèque à Lucilius - Livre Sixième.''], [https://books.google.fr/books?id=xZtfAAAAcAAJ&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles&hl=fr&pg=PA429#v=onepage&q&f=true ''chap. VI.''], traduction française de la collection Panckoucke, nouvelle édition très soigneusement revue par M. Charpentier et M. Félix Lemaistre, 1860</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">'''Chap. VI.'''<br />''Si l’eau e[[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki>t</nowiki> la cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e des tréblemés de terre.''<br /><p style="text-align: justify; text-indent: 15px;">Ce n'e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t pas vn homme {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eul qui a dit , que l’eau e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}toit cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e du tremblement de la terre ; & l’on ne l’a pas dit d’vne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eule façó. '''Thales''' ''Mile{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ien'' a crû que toute la terre e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}toit portée {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur l’eau , & qu’elle y nageoit, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oit que vous appelliez cette eau Ocean, ou que vous l’appelliez grade mer, ou vne eau d’vne autre nature , eau {{Info|ſ|forme ancienne longue de la lettre s minuscule}}imple , element humide. C’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur cette eau, dit-il, que le monde e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tenu, comme quelque vai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eau d’vne grandeur deme{{Info|ſ|forme ancienne longue de la lettre s minuscule}}urée , qui charge les eaux qui le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tiennent. Il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit inutile de rapporter les rai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ons qui luy {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont croire que la plus pe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ante partie du móde ne peut e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tre {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tenuë par l’air qui e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ubtil, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i fluide & {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i delié ; & d’ailleurs, il ne s’agit pas icy de l’a{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}iete de la terre , mais du tremblement de la terre. Ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i pour preuue que les eaux {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e que la terre tremble , il dit qu’il ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e fait pre{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que point de grands tremblemens de terre, qu’on n’en voye {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortir en{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uitte de nouuelles {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ources; que la terre re{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}emble en cela aux vai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eaux qui ne peuuent pancher d’vn co{{Info|ſ|forme ancienne longue de la lettre s minuscule}}té, qu’ils ne pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ent de l’eau, qui {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pand {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur toutes les cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es qu’ils portent , {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i elles {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont trop enfoncées; ou qui s’éleue de part & d’autre à la gauche , & à la droite. Il n’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t pas besoin d’vn long di{{Info|ſ|forme ancienne longue de la lettre s minuscule}}cours pour montrer la fau{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eté de cette opinion ; car {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i l’eau {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tenoit la terre, quelquesfois elle trembleroit toute entiere, & {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit tou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iours en mouuement; & nous ne nous e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tonnerions point de la voir remuer, mais de la voir ferme & inébranlable. Elle trembleroit toute entiere , & non pas en partie , car vn vai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eau n’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t iamais agité par vne moitié {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement ; & apres tout nous voyons que le tremblement ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e fait pas de toute la terre , mais {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement d’vne partie. Comment donc {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e peut-il faire que ce qui e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t porté tout entier ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oit pas entierement agité , {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i la cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}me qui porte e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}meuë & agitée ? Mais pourquoy {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ort-il de l’eau apres vn tremblement de terre ? Premierement la terre a {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ouuent tremblé {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ans qu’on en ayt veu {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortir de nouuelles {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ources. D’ailleurs {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i l’eau {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortoit par cette rai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}on elle {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pandroit par les co{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tez de la terre , comme nous voyons dans les fleuues & dans la mer , où lors que le vai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eau s’enfonce on remarque que l’eau s'éleue , principalement par les co{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tez. Enfin ces eaux ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortiroient pas en {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i petite quantité, ny par vne fi petite ouuerture , mais il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit vne grande inondation, comme procedant de cette abondance d’eaux qui {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tiennent tout l’vniuers.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=gEzVYlz3c3cC&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii%20volume%202&hl=fr&pg=PA1#v=onepage&q&f=true <u>Seneque Des Qvestions Natvrelles</u>], [https://books.google.fr/books?id=gEzVYlz3c3cC&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii%20volume%202&hl=fr&pg=PA141#v=onepage&q&f=true ''Livre Sixiesme. Des tremblemens de terre.''], [https://books.google.fr/books?id=gEzVYlz3c3cC&newbks=1&newbks_redir=0&dq=seneque%20questions%20naturelles%20iii%20volume%202&hl=fr&pg=PA164#v=onepage&q&f=true ''chap. VI.''], traduction par [[w:Pierre_Du_Ryer|Pierre Du Ryer]], A Lyon, Chez Christofle Fovrmy, 1663</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Pline_l%27Ancien|'''Pline l’Ancien''']] [[#Pline|<span id="Pline_back"><sup>'''I'''</sup></span>]] == <p style="text-align: right;">([[w:23|23]]/[[w:24|24]], à [[w:Côme|''Novum Comum'']] ou [[w:Vérone|Vérone]] — [[w:79|79]], à [[w:Stabies|Stabies]], [[w:Mort_de_Pline_l%27Ancien|mort]] par asphyxie près de [[w:Pompéi|Pompéi]], lors de l’éruption du [[w:Vésuve|Vésuve]], en voulant observer le phénomène au plus près et en désirant porter secours aux victimes, alors en poste à [[w:Misène|''Misène'']] en tant que Préfet commandant la flotte militaire ''romaine'') <sup>[[w:Ier_siècle|⏳]]</sup> [[s:Auteur:Pline_l’Ancien|<sup>📚</sup>]] [https://books.google.fr/books?id=tRsuD3WJT-UC&newbks=1&newbks_redir=0&lpg=PA3&dq=bibliogroup%3A%22Dictionnaire%20des%20philosophes%20antiques%22&hl=fr&pg=PA876#v=onepage&q&f=true {{Info|<sup>🔍</sup>|Dictionnaire des Philosophes Antiques, publié sous la direction de Richard Goulet, Volume Va, §204 - Plinius Secundus (Caius —)}}] <div style="text-align: justify; margin: 0 1em; text-indent: 15px">Haut fonctionnaire militaire et civique, issu de l’[[w:Chevalier_romain|''orde équestre'']], et écrivain prolifique dans de très nombreux domaines.</div> {{Boîte déroulante début|titre=NdA Pline l’Ancien|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Pline_back|<span id="Pline"><sup>I</sup></span>]] [[w:Nom_romain|Tria Nomina]] en latin {{Info|Caius|praenomen, nom individuel du citoyen romain}} {{Info|Plinius|nomen, nom de famille}} {{Info|Secundus|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}}'''<br/><br/></div> {{Boîte déroulante fin}} === [[w:Histoire_naturelle_(Pline_l'Ancien)|Histoire naturelle]] === <p style="text-align: right;">[[s:Histoire_naturelle_(Pline)|📚]] <div style="text-align: justify; margin: 0 1em; text-indent: 15px">Ouvrage de 37 livres dédié à l’empereur [[w:Titus_(empereur_romain)|'''Titus''']] [[#Titus|<span id="Titus_back"><sup>'''I'''</sup></span>]], dont il a été le ''{{Lang|la|contubernium}}'' pendant son service en tant que commandant des armées du ''Rhin'' en ''Germanie'' en [[w:47|47]]. '''Pline''' définit lui-même son enquête [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Histoire/Enquête_I|<sup>🔄</sup>]] sur la nature comme une reproduction de la vie — ''{{Lang|la|rerum natura, hoc est uita narratur}}'' (Livre I, Préface, 10), qu’il inscrit dans la tradition encyclopédique [[#encyclopédie_back|<sup>⤴️</sup>]] grecque mais s’en différencie par son exhaustivité : ''{{Lang|la|Jam omnia attingenda, quæ Græci}}'' ''{{Lang|grc|τάς έγχυχλοπαιδείας}}'' ''{{Lang|la|vocant : et tamen ignota aut incerta ingeniis facta; alia vero ita multis prodita, ut in fastidium sint adducta}}'' (Préface, 11). Il a, dans ce but, compilé « vingt mille faits dignes d’intérêt, tirés de la lecture d’environ deux mille volumes, [...] provenant de cent auteurs de choix » — ''{{Lang|la|Viginti millia rerum dignarum cura ex lectione voluminum circiter duum millium, [...] ex exquisitis auctoribus centum}}'' (Préface, 13).</div> {{Boîte déroulante début|titre=NdA Titus|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Titus_back|<span id="Titus"><sup>I</sup></span>]] Du nom propre latin Titus [[wikt:en:Titus#Latin|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">Commandant militaire, notamment pendant la [[w:Premi%C3%A8re_guerre_jud%C3%A9o-romaine|''première guerre judéo-romaine'']], au cours de laquelle il prend ''Jérusalem'' que ses troupes mettent à sac et fait détruire le [[w:Second_temple_de_Jérusalem#Destruction|''Second Temple'']], et empereur ''romain'', de la dynastie des [[w:Flaviens|''Flaviens'']], de [[w:79|79]] à [[w:81|81]].<br /><p style="text-align: right; margin: 0 2em;">(30 décembre [[w:39|39]] , ''Rome'' — 13 septembre [[w:81|81]], mort par fièvre selon [[w:Suétone|Suétone]] [http://remacle.org/bloodwolf/historiens/suetone/titus.htm <sup>VdDC, Titus</sup>] ou par empoisonnement avec du venin de [[w:Aplysia|''lièvre marin'']] par son propre frère [[w:Domitien|Domitien]] selon [[w:Philostrate_d%27Ath%C3%A8nes|Philostrate]] [https://remacle.org/bloodwolf/roman/philiostrate/apollonius6.htm <sup>AdT, l. VI, chap. 32</sup>])<sup>[[w:Ier_siècle|⏳]]</sup>'''<br/><br/></div> {{Boîte déroulante fin}} ==== Livre II ==== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Enquête sur l’astronomie et la physique du monde, basée sur les quatre éléments : air, terre, eau et feu.</div> ===== <div style="text-align: center;">Chapitre IX.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de la première prédiction grecque d’une éclipse solaire (ou lunaire selon les traductions) par '''Thalès'''.</div> :'''Texte latin''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''IX.''' Et rationem quidem defectus utriusque primus ''Romani'' generis in vulgus extulit '''Sulpicius Gallus''', qui consul cum '''Marcello''' fuit, sed tum tribunus militum, sollicitudine exercitu liberato, pridie quam '''Perseus''' rex superatus a '''Paulo''' est, in concionem ab imperatore productus ad prædicendam eclipsim, mox et composito volumine. Apud Græcos autem investigavit primus omnium '''Thales''' ''Milesius'', Olympiadis XLVIII anno quarto, prædicto solis defectu, qui '''Alyatte''' rege factus est, Urbis conditæ; anno CLXX. Post eos utriusque sideris cursum in sexcentos annos præcinuit '''Hipparchus''', menses gentium, diesque et horas, ac situs locorum, et visus populorum complexus, ævo teste, haud alio modo, quam consiliorum naturæ particeps. Viri ingentes supraque mortalium naturam, tantorum numinum lege deprehensa, et misera hominum mente absoluta, in defectibus scelera aut mortem aliquam siderum pavente (quo in metu fuisse '''Stesichori''' et '''Pindari''' vatum sublimia ora palam est deliquio Solis), et in Luna veneficia arguente mortalitate, et ob id crepitu dissono auxiliante. Quo pavore, ignarus causæ, '''Nicias''' ''Atheniensium'' imperator, veritus classem portu educere, opes eorum afflixit. Macti ingenio este, cæli interpretes, rerumque naturæ capaces, argumenti repertores, quo deos hominesque vinxistis. Quis enim hæc cernens, et statos siderum (quoniam ita placuit appellare) labores, non suæ necessitati mortalis genitus ignoscat ? Nunc confessa de iisdem breviter atque capitulatim attingam, ratione admodum necessariis locis strictimque reddita : nam neque instituti operis talis argumentatio est : neque omnium rerum afferri posse causas, minus mirum est, quam consfare in aliquibus.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/b24875958_0001/page/n7/mode/2up <u>Histoire Naturelle de Pline.</u>], [https://archive.org/details/b24875958_0001/page/98/mode/2up ''Livre II.''], [https://archive.org/details/b24875958_0001/page/106/mode/2up ''chap. IX.''], texte corrigé par [[w:Émile_Littré|M. É. Littré]], à partir de celui de [[w:Jean_Hardouin|Hardouin]], Librairie de Firmin-Didot et C<sup>ie</sup>, Paris, 1883<br />(également disponible une édition 1848 [[s:Page:Pline_l'ancien_-_Histoire_naturelle,_Littré,_T1_-_1848.djvu/130|ici]] et 1829 [https://gallica.bnf.fr/ark:/12148/bpt6k5773334c/f45.item là])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''IX.''' Le premier ''Romain'' qui exposa publiquement la théorie des éclipses du soleil et de la lune est [[w:Caius_Sulpicius_Gallus|'''Sulpicius Gallus''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Caius_Sulpicius_Gallus_back|<sup>🔄</sup>]], qui fut consul avec [[w:Marcus_Claudius_Marcellus_(consul_en_-166)|'''Marcellus''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Marcellus-166_back|<sup>🔄</sup>]], mais qui alors était tribun militaire. La veille du jour où [[w:Persée_(roi)|'''Persée''']] [[#Persée_(roi)|<span id="Persée_(roi)_back"><sup>'''I'''</sup></span>]] fut défait par [[w:Lucius_Æmilius_Paullus_Macedonicus|'''Paul-Emile''']] [[#Lucius_Æmilius_Paullus_Macedonicus|<span id="Lucius_Æmilius_Paullus_Macedonicus_back"><sup>'''II'''</sup></span>]] il parut par ordre du général, afin de prévenir les alarmes de l’armée, devant les troupes assemblées pour annoncer l’éclipse qui allait survenir; peu de temps après, il composa un livre sur ce sujet. Le premier qui s’en occupa chez les ''Grecs'' fut '''Thalès''' de ''Milet'', dans la quatrième année de la quarante-huitième olympiade (an 585 av. J. C. [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#éclipse_back|<sup>🔄</sup>]]), l’an 170 de la fondation de ''Rome'', et prédit une éclipse de lune qui arriva sous le roi [[w:Alyatte_II|'''Alyatte''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Alyatte_back|<sup>🔄</sup>]]. Après eux, [[w:Hipparque_(astronome)|'''Hipparque''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Hipparque_back|<sup>🔄</sup>]] dressa pour six cents ans la table du cours du soleil et de la lune, déterminant les mois des divers calendriers, les jours, les heures, les localités et les aspects, suivant les contrées. Le cours des ans ne lui a donné aucun démenti, et il semble avoir été admis aux conseils de la nature. Génies puissants et élevés au dessus de l’humanité, ils ont découvert la loi qui régit ces grandes divinités, et ils ont délivré de ses craintes l’esprit misérable des hommes, qui dans les éclipses, tantôt croyaient voir une influence malfaisante ou une espèce de mort des astres, crainte qui, comme on sait, a, pour l’éclipse du soleil, troublé [[w:Stésichore|'''Stésichore''']] [[#Stésichore|<span id="Stésichore_back"><sup>'''III'''</sup></span>]] et [[w:Pindare|'''Pindare''']] [[#Pindare|<span id="Pindare_back"><sup>'''IV'''</sup></span>]], poètes sublimes, et tantôt attribuaient l’obscurcissement de la lune à des maléfices, et lui venaient en aide par un bruit dissonnant. Redoutant ce phénomène, dont il ignorait la cause, [[w:Nicias|'''Nicias''']] [[#Nicias|<span id="Nicias_back"><sup>'''V'''</sup></span>]], général des ''Athéniens'', n’osa pas faire sortir la flotte du port de [[w:Syracuse#Antiquité|''Syracuse'']], et ruina la puissance de sa patrie. Redoublez de génie, interprètes du ciel, vous dont l’intelligence, embrassant la nature, a inventé des théories qui ont créé un lien entre les dieux et les hommes [[#Vicistis_JH|<span id="Vicistis_JH_back"><sup>'''1'''</sup></span>]] ! A la vue de ce spectacle, à la vue des labeurs (puisque c’est le nom qu’on a voulu donner aux éclipses), des labeurs réguliers auxquels les astres sont soumis, quel mortel ne pardonnerait à la nécessité sous laquelle il est né ? Maintenant je vais parler, d’une manière brève et sommaire, des points sur lesquels on est d’accord en cette matière. Je ne donnerai que de courtes explications, et là où il sera tout à fait nécessaire; car les explications n’entrent pas dans le plan de cet ouvrage, et il n’y a pas moins de mérite à énumérer les causes de toutes choses qu’à s’appesantir sur quelques-unes.</div> <table cellspacing=15 align=center style="margin: 0 4em; font-size:85%;"> <tr> <td style="text-align: justify; margin: 0 4em;">'''[[#Vicistis_JH_back|<span id="Vicistis_JH"><sup>1</sup></span>]] Vicistis Vulg. — Vinxistis cod. Dalech. — Vinxistis me parait meilleur. Comp. ce que dit Pline plus loin, ch. 24, sur l’affinité de l’esprit humain avec les astres.'''</td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/b24875958_0001/page/n7/mode/2up <u>Histoire Naturelle de Pline.</u>], [https://archive.org/details/b24875958_0001/page/98/mode/2up ''Livre II.''], [https://archive.org/details/b24875958_0001/page/106/mode/2up ''chap. IX.''], texte corrigé par [[w:Émile_Littré|M. É. Littré]], à partir de celui de [[w:Jean_Hardouin|Hardouin]], Librairie de Firmin-Didot et C<sup>ie</sup>, Paris, 1883<br />(édition 1848 également disponible [[s:Page:Pline_l'ancien_-_Histoire_naturelle,_Littré,_T1_-_1848.djvu/130|ici]])</div> {{Boîte déroulante début|titre=NdA de trad. Jean Hardouin 1883|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Persée_(roi)_back|<span id="Persée_(roi)"><sup>I</sup></span>]] Du nom propre grec ancien Περσεύς / Perseús[[wikt:en:Περσεύς#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">Dernier roi de [[w:Royaume_de_Mac%C3%A9doine|''Macédoine'']] de la dynastie des [[w:Antigonides|''Antigonides'']], vaincu en [[w:-168|-168]] à la [[w:bataille de Pydna|bataille de ''Pydna'']] à l’issue de la [[w:troisième guerre macédonienne|''troisième guerre macédonienne'']], causant la disparition du ''Royaume de Macédoine''.<br /><p style="text-align: right; margin: 0 2em;">([[w:Années_212_av._J.-C.|-212]]<sup>[[w:IIIe_siècle_av._J.-C.|⏳]]</sup>, [[w:Pella_(cité_antique)|''Pella'']], au nord de l’actuelle ''Grèce'' — [[w:Années_166_av._J.-C.|-166]]<sup>[[w:IIe_siècle_av._J.-C.|⏳]]</sup>, [[w:Alba_Fucens|''Alba Fucens'']], au centre de l’actuelle ''Italie'') <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Lucius_Æmilius_Paullus_Macedonicus_back|<span id="Lucius_Æmilius_Paullus_Macedonicus"><sup>II</sup></span>]] [[w:Nom_romain|Tria Nomina]] en latin {{Info|Lucius|praenomen, nom individuel du citoyen romain}} {{Info|Æmilius|nomen, nom de famille}} {{Info|Paullus|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}}, surnommé [[wikt:Macedonicus#Latin|Macedonicus]] par le [[w:Sénat_romain|''Sénat'']] à la suite de sa victoire;<br /><p style="margin: 0 2em; text-indent: 15px">Homme politique ''romain'', consul à 2 reprises en [[w:Années_182_av._J.-C.|-182]] et [[w:Années_169_av._J.-C.|-169]]. Il remporta la victoire contre le ''royaume de Macédoine'' à ''Pydna'' en battant le roi Persée [[#Persée_(roi)|<sup>I</sup>]], ce qui mit fin à la dynastie des ''Antigonides''.<br /><p style="text-align: right; margin: 0 2em;">([[w:Circa|{{Info|''ca.''|Circa, locution latine que l’on emploie pour indiquer l’approximation d’une date}}]] [[w:Années_230_av._J.-C.|-230]]<sup>[[w:IIIe_siècle_av._J.-C.|⏳]]</sup>, ''Rome'' — ''ca.'' [[w:Années_160_av._J.-C.|-160]]<sup>[[w:IIe_siècle_av._J.-C.|⏳]]</sup>, ''Rome'') <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Stésichore_back|<span id="Stésichore"><sup>III</sup></span>]] Du nom propre grec ancien Στησίχορος /Stēsíkhoros [[wikt:en:Στησίχορος#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">➥ du verbe ἵστημι /hístēmi, « 1. (voix transitive, active des temps présent, imparfait, futur et 1er aoriste) : • Faire se tenir debout, se tenir debout; • Arrêter, rester, vérifier; • Mettre en place : - Faire monter, élever, réveiller, remuer; - Nommer, désigner; - Établir, instituer; • Mettre dans la balance, peser. 2. (voix intransitive, moyenne et passive, voix active du 2e aoriste, parfait et plus-que-parfait) : • Se tenir debout; • Se tenir immobile : (au sens figuré) Rester ferme; • Être dressé ou debout, se lever, s’élever : - (en général) Se lever, commencer; - (en marquant le pas) Être; - Être désigné. »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ +‎ du nom commun χορός / khorós, « 1. Danse en rond. 2. Danse accompagnée de chant, danse chorale. 3. Chœur, chorale, groupe de chanteurs et de danseurs. 4. Groupe, troupe. 5. Rangée. 6. Lieu de danse. 7. (théâtre) Chœur »;<br /><p style="margin: 0 2em; text-indent: 15px">Poète lyrique grec, considéré comme l’un des [[w:Neuf_lyriques_grecs|''neuf poètes lyriques'']] de la Grèce antique.<br /><p style="text-align: right; margin: 0 2em;">([[w:Circa|{{Info|''ca.''|Circa, locution latine que l’on emploie pour indiquer l’approximation d’une date}}]] [[w:Années_630_av._J.-C.|-630]]<sup>[[w:VIIe_siècle_av._J.-C.|⏳]]</sup>, [[w:Metauria|''Metauria'']], colonie de la [[w:Grande-Grèce|''Grande-Grèce'']], au sud de la région de [[w:Calabre|''Calabre'']], au sud de l’Italie — ''ca.'' [[w:Années_555_av._J.-C.|-555]]<sup>[[w:VIe_siècle_av._J.-C.|⏳]]</sup>, [[w:Catane|''Catane'']], colonie de la ''Grande-Grèce'', à l’est de la [[w:Sicile|''Sicile'']]) <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Pindare_back|<span id="Pindare"><sup>IV</sup></span>]] Du nom propre grec ancien Πίνδᾰρος /Píndaros [[wikt:en:Πίνδαρος#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">Poète lyrique grec, considéré comme l’un des [[w:Neuf_lyriques_grecs|''neuf poètes lyriques'']] de la Grèce antique.<br /><p style="text-align: right; margin: 0 2em;">([[w:Années_518_av._J.-C.|-518]]<sup>[[w:VIe_siècle_av._J.-C.|⏳]]</sup>, [[w:Cynocéphales|''Cynocéphales'']], cité grecque située près de [[w:Thèbes_(Grèce)|''Thèbes'']], en [[w:Béotie|''Béotie'']] — [[w:Années_438_av._J.-C.|-438]]<sup>[[w:Ve_siècle_av._J.-C.|⏳]]</sup>, [[w:Árgos|''Árgos'']], cité grecque de la région de l’[[w:Argolide|''Argolide'']], à l’est de la [[w:Péloponnèse|''péninsule du Péloponnèse'']]) <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Nicias_back|<span id="Nicias"><sup>V</sup></span>]] Du nom propre grec ancien Νῑκίᾱς /Nīkíās [[wikt:en:Νικίας#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">➥ du nom commun νίκη / níkē [[wikt:en:νίκη#Ancient_Greek|(en)]], « 1. Le fait de gagner : la victoire, le succès [avec le génitif "sur, dans quelque chose"] : • Les choses gagnées dans la victoire, les fruits de la victoire; • La supériorité, l’avantage. »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ + du suffixe nominal masculin -ίας / -ías [[wikt:en:-ίας#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">Homme politique et général ''athénien'' durant la [[w:Guerre_du_Péloponnèse|''guerre du Péloponnèse'']], qui oppose la [[w:Ligue_de_Délos|''ligue de Délos'']], menée par ''Athènes'', et la [[w:Ligue_du_Péloponnèse|''ligue du Péloponnèse'']], sous l’[[w:Hégémonie|hégémonie]] de ''Sparte'' de [[w:Années_431_av._J.-C.|-431]] à [[w:Années_404_av._J.-C.|-404]]. Sa supersition liée à une éclipse lunaire, s’étant produite lors de l’[[w:Expédition_de_Sicile|''expédition de Sicile'']], est également relatée par [[w:Thucydide|Thucydide]] [http://remacle.org/bloodwolf/historiens/thucydide/livre7.htm#L <sup>{{Info|HdlgdP|Histoire de la guerre du Péloponnèse}} l.VII, §.L</sup>] et [[w:Plutarque|Plutarque]] [[#Plutarque_back|<sup>⤵️</sup>]] [http://remacle.org/bloodwolf/historiens/Plutarque/supestition.htm#23 <sup>{{Info|DlS|De la Superstition}} l.I</sup>].<br /><p style="text-align: right; margin: 0 2em;">([[w:Circa|{{Info|''ca.''|Circa, locution latine que l’on emploie pour indiquer l’approximation d’une date}}]] [[w:Années_470_av._J.-C.|-470]], ''Athènes'' — ''ca.'' [[w:Années_413_av._J.-C.|-413]], ''Syracuse'')<sup>[[w:Ve_siècle_av._J.-C.|⏳]]</sup>'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: center; margin: 0 2em;">Des découvertes astronomiques : part de chaque observateur dans la science.<br /><p style="text-align: justify; text-indent: 15px;">'''IX.''' '''Sulpicius Gallus''' [[#Sulpicius_Gallus_AdG|<span id="Sulpicius_Gallus_AdG_back"><sup>'''1'''</sup></span>]] fut le premier ''Romain'' qui expliqua au vulgaire la raison des éclipses de soleil et de lune. Il fut consul avec '''Marcus Marcellus''' ; mais il n’était que tribun militaire lorsque la veille de la victoire que '''Paul Emile''' remporta sur '''Persée''' [[#Pridie_quam_Perses_rex_AdG|<span id="Pridie_quam_Perses_rex_AdG_back"><sup>'''2'''</sup></span>]], son général le fit paraître devant l’armée assemblée, pour lui annoncer l’éclipse qui allait arriver, et la délivrer de l’alarme qu’elle aurait pu en concevoir. Il composa bientôt après un volume sur ce sujet. Parmi les ''Grecs'', '''Thalès''' de ''Milet'' [[#Primus_omnium_Thales_AdG|<span id="Primus_omnium_Thales_AdG_back"><sup>'''3'''</sup></span>]] dirigea le premier ses recherches sur ce phénomène, et la quatrième année de la 48e olympiade, qui répond à l’an 170 [[#Anno_CLXX_AdG|<span id="Anno_CLXX_AdG_back"><sup>'''4'''</sup></span>]] de ''Rome'', il prédit l’éclipse de soleil qui eut lieu sous le règne d’'''Alyatte''' [[#Alyatte_rege_AdG|<span id="Alyatte_rege_AdG_back"><sup>'''5'''</sup></span>]]. Après eux, '''Hipparque''' dressa des tables du cours de ces deux astres pour six cents ans [[#In_sexcentos_annos_AdG|<span id="In_sexcentos_annos_AdG_back"><sup>'''6'''</sup></span>]] : mois, heures, jours, situations respectives des lieux, aspects du ciel selon les diverses nations [[#Menses_gentium_etc_AdG|<span id="Menses_gentium_etc_AdG_back"><sup>'''7'''</sup></span>]], tout y est compris, tout a été vérifié par le temps [[#Aevo_teste_AdG|<span id="Aevo_teste_AdG_back"><sup>'''8'''</sup></span>]]; on croirait l’astronome admis au conseil de la nature. Génies vastes et plus qu’humains, d’avoir ainsi surpris la loi de ces deux grandes divinités [[#Numinum_AdG|<span id="Numinum_AdG_back"><sup>'''9'''</sup></span>]], et affranchi d’effroi la malheureuse espèce humaine, qui tremblait en voyant dans chaque éclipse l’annonce de quelque grand crime, ou craignait la mort des astres [[#In_defectibus_scelera_etc_AdG|<span id="In_defectibus_scelera_etc_AdG_back"><sup>'''10'''</sup></span>]] (effroi dont '''Stésichore''' et '''Pindare''' [[#Pindari_AdG|<span id="Pindari_AdG_back"><sup>'''11'''</sup></span>]], ces poètes sublimes, ne furent point exempts dans les éclipses de soleil), ou qui attribuait à des enchantemens celles de la lune, et venait à son secours en faisant un bruit discordant [[#Crepitu_dissono_AdG|<span id="Crepitu_dissono_AdG_back"><sup>'''12'''</sup></span>]]. C’est pour en avoir ignoré la cause, que, frappé de cette même terreur, '''Nicias''' [[#Nicias_AdG|<span id="Nicias_AdG_back"><sup>'''13'''</sup></span>]], général des ''Athéniens'', n’osa pas faire sortir sa flotte du port, et causa la ruine de leur puissance. Gloire à vous, interprètes du ciel, génies aussi étendus que la nature, inventeurs d’une science qui enchaîne à une même destinée les dieux et les mortels ! Quel est donc l’homme qui, voyant les astres en travail (pour me servir du nom qu’il a plu de donner aux crises qu’ils, subissent périodiquement), ne se soumettra pas à sa destinée?<br /><p style="text-align: justify; text-indent: 15px;">Je vais maintenant toucher brièvement et sommairement les points sur lesquels on est d’accord dans cette matière, et j’en rendrai raison en passant, lorsque cela sera tout-à-fait nécessaire; car un développement de preuves n’est pas le but de l’ouvrage que j’ai entrepris, et il n’y a pas, je pense, moins de mérite à pouvoir rendre raison de toutes choses, qu’à s’arrêter à en prouver quelques-unes.</div> {{Boîte déroulante début|titre=Notes du traducteur|alignT=center}} <div style="text-align: justify; border: 2px; border-radius:15px; font-size:85%;"><br/> <table cellspacing=15 align=center style="margin: 0 4em;"> <tr> <td style="text-align: justify; margin: 0 4em; text-indent: 15px">'''[[#Sulpicius_Gallus_AdG_back|<span id="Sulpicius_Gallus_AdG"><sup>1</sup></span>]] [[w:Tite-Live|Tite-Live]], XLIV, 37, [[w:Quintilien|Quintilien]], I, 10, [[w:Plutarque|Plutarque]] [[#Plutarque_back|<sup>⤵️</sup>]], [[w:Vies_parallèles|''Vie de Paul Emile'']], [[w:Frontin|Frontin]], I, etc., prétendent, comme Pline, que Sulpicius Gallus prédit l’éclipse anx soldats romains. [[w:Cicéron|'''Cicéron''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Cicéron_back|<sup>🔄</sup>]] ([[w:De_Republica|''Répub.'']], I, 15 , page 44, ''édit. Maj.'') dit au contraire que l’éclipse était déjà arrivée lorsque Sulpicius Gailus commençait à s’efforcer d’ôter aux soldats romains la terreur qu’ils avaient conçue de cet événement, en leur expliquant les causes des éclipses.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Pridie_quam_Perses_rex_AdG_back|<span id="Pridie_quam_Perses_rex_AdG"><sup>2</sup></span>]] Selon Tite-Live (XLIV, 37), l’éclipse eut lieu dans la soirée du 3 septembre, cent huit ans avant J.-C., entre sept et dix heures (''ab hora secunda usque ad quartam noctis, quam pridie nonas sept, secula est dies''). M. [[w:Christian_Ludwig_Ideler|Ideler]] (''Chronologie'', II, 104) a calculé cette éclipse. Il a trouvé, comme M. de Nauze, que, selon le calendrier Julien, elle arriva dans la soirée du 21 juin de l’an 168 avant J.-C. à Rome, la lune commença à s’éclipser vers 5h.44’ du soir ; depuis 6h.51’ jusqu’à 8h.18’, la lune fut totalement éclipsée, ; à 9h.,24’, la lune ne fut plus obscurcie du tout. En Macédoine, tous ces phénomènes arrivèrent 39 minutes plus tard. Le 21 juin, le soleil se coucha à Rome et en Macédoine vers 7h.33’, et s’y montra alors à 44 1/2’ h. équatoriales. Ainsi, la première heure de la nuit finissait vers 8h.17’, la seconde vers 9h.2’, la troisième vers 9h.46’, la quatrième vers 10h.31’. La lune fut donc éclipsée totalement au moment où elle se leva dans la Macédoine, et cette éclipse totale y finit dans la seconde heure de la nuit ; au milieu de la quatrième, la lune ne fut plus obscurcie du tout. S’il est vrai, que cette éclipse lunaire, comme Pline, Tite-Live et d’autres le disent, fut prédite par Sulpicius Gallus, l’on devrait avouer que ce Romain s’entendait très-bien au calcul des éclipses lunaires. Mais les récits de Cicéron et de [[w:Valère_Maxime|Valère-Maxime]] ne seraient-ils pas plus vrais que ceux de Pline et de Tite-Live ?''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Primus_omnium_Thales_AdG_back|<span id="Primus_omnium_Thales_AdG"><sup>3</sup></span>]] Le même fait est rapporté par [[w:Hérodote|Hérodote]] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Hérodote_back|<sup>🔄</sup>]], I, 74, par [[w:Diogène_Laërce|Diogène Laërce]] [[#Diogène_Laërce_back|<sup>⤵️</sup>]], I, 6 , par [[w:Clément_d'Alexandrie|Clément d’Alexandrie]] [[#Clément_d'Alexandrie_back|<sup>⤵️</sup>]], ''Strom.'', I, page 302 , par Plutarque, ''Opinions des Philosophes'', II, 24, par [[w:Jean_Tzétzès|Tzetzès]], ''Chil.'' II, v. 869, et par Hardouin.<br /><p style="text-align: justify; text-indent: 15px;">Oltmann a publié une dissertation dans laquelle, à l’aide des tables astronomiques les plus modernes, il est arrivé à ce résultat, que l’éclipse solaire dont il est question ici eut lieu le 3o septembre 610 ans avant J.-C. L’éclipse était totale pour les environs de la ville d’[[w:Erzurum|Érzerum]] sur le [[w:Kızılırmak_(fleuve)|Halys]] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Halys_back|<sup>🔄</sup>]], où [[w:Volney|Volney]] place le champ de bataille des rois [[w:Alyatte_II|Halyattes]] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Alyatte_back|<sup>🔄</sup>]] et [[w:Cyaxare|Cyaxare]] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Cyaxare_back|<sup>🔄</sup>]]. La quatre-vingtième partie du disque solaire seule ne fut pas éclipsée pour le lieu de la terre où Oltmann place le champ de bataille, qui est situé, selon lui, sous 36° long. à l’est de Terro et sous 40° lat. sept. Dans le pays des Ioniens où Thalès prédit l’éclipse, selon Hérodote, l’éclipse se monta à 11 1/2’. M. [[w:Alphonse_Des_Vignoles|Desvignolles]] (''Chronologie de l’histoire sainte'', t. II, pag. 245 et suiv.) fixe l’éclipsé prédite par Thales au 38 mai de l’an 585 avant J.-C. ; son opinion a été adoptée depuis par presque tous les chronologistes et historiens, et par [[w:Gabriel_Brotier|Brotier]] et M. Alexandre. Elle a été réfutée avec succès par Oltmann, qui s’est servi de tables astronomiques bien plus exactes que celles de M. Desvignolles ; en effet, celui-ci a démontré par ses calculs que l’éclipse totale du soleil du 28 mai de l’an 585 avant J.-C., ne fut pas totale dans les lieux où les troupes du roi lydien Halyatte combattirent contre celles du roi Cyaxare ; que, dans ces régions, elle ne se monta pas à plus de 7 1/2 pouces, et que, d’ailleurs, le soleil ne s’était pas encore levé lorsque Féclipse était le plus forte pour les habitans des pays nommés. Mais Hérodote dit positivement que l’éclipse prédite par Thalès fut totale dans ces contrées, et qu’elle eut lieu en plein jour. (Voyez IDELER, ''Chronologie'', t. I, pag. 209 et 210.) Nous remarquerons pourtant avant de finir cette note que la date de Desvignolles est plus conforme que celle d’Oltmann à l’année dans laquelle l’éclipse prédite par Thalès, arriva selon Pline. En effet, le naturaliste romain dit que cette année est la six cent quatre-vingt-cinquième avant J.—C., et c’est justement le 28 mai de cette année que l’éclipse prédite par Thalès arriva, selon M. Desvignolles.<br /><p style="text-align: justify; text-indent: 15px;">Volney pense que l’éclipse en question arriva le 3 février de de l’an 626. Oltman a démontré, dans son Mémoire sur l’éclipse de Thalès, que celle de Volney était déjà passée lorsque le soleil se leva sur le champ de bataille des rois Halyattes et Cyaxare.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Anno_CLXX_AdG_back|<span id="Anno_CLXX_AdG"><sup>4</sup></span>]] L’an 170. C’est ainsi que l’on doit lire, et non CLX, comme l’ont fait Hardouin et Poinsinet; la quatrième année de la quarante-huitième olympiade, correspondant à l’an 170 de Rome, si l’on suppose avec [[w:Varron_(écrivain)|Varron]] que cette ville a été fondée dans la deuxième année de la 6e olympiade.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Alyatte_rege_AdG_back|<span id="Alyatte_rege_AdG"><sup>5</sup></span>]] Le nom de ce roi est écrit avec un esprit rude dans Hérodote, ce qui a donné lieu à [[w:Louis_Poinsinet_de_Sivry|Poinsinet]] et à d’autres traducteurs de Pline de substituer le mot Halyatte à celui d’Alyatte.<br /><p style="text-align: justify; text-indent: 15px;">Alyatte ou Halyatte fut roi de la Lydie et père de Crésus. Il faisait la guerre à Cyaxare, roi des Mèdes, lorsque l’éclipse solaire en question interrompit le combat.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#In_sexcentos_annos_AdG_back|<span id="In_sexcentos_annos_AdG"><sup>6</sup></span>]] On lit dans [[w:Georges_le_Syncelle|le Syncelle]] (''Chronolog.'', pag. 17) que les Chaldéens ont connu une période de six cents années solaires. [[w:Flavius_Josèphe|Josèphe]] [[#Flavius_Josèphe_back|<sup>⤵️</sup>]] ([[w:Antiquités_judaïques|''Ant. jud.'']], I, page 17 et 18, édit. Havercamp) dit que Dieu donna une longue vie aux patriarches pour qu’ils pussent cultiver avec succès les sciences astronomiques et géométriques, ce qu’ils n’auraient pu faire s’ils n’avaient pas vécu au moins six cents ans ; car la grande année ne finit pas plus tôt. Ainsi, il est certain qu’avant Hipparque les Chaldéens et d’autres peuples asiatiques ont connu une période de six cents années solaires. Mais [[w:Jean-Dominique_Cassini|Cassini]] ([[w:Jean-Dominique_Cassini#Mémoires_de_l’Académie_royale_des_sciences|''Anciens mém. de l’Acad.'']], t. VIII, pag. 4 et 5) et [[w:Jean_Sylvain_Bailly|Bailly]] (''Hist. de l’astr. ancienne'', t. II, liv. 3, Eclairciss.) ont prouvé que tous les six cents ans les nouvelles et pleines lunes n’arrivent pas seulement au même jour et à la même heure qu’auparavant, mais encore à la même minute. Ne serait-il donc pas probable qu’Hipparque, comme le dit Ideler (''Historische untersuchungen uber die astronomischen beobachtungen der alten'', Berlin 1806, page 417) a connu cette période chaldéenne, et que delà résulte l’étendue de six cents ans donnée à son calendrier selon Pline.<br /><p style="text-align: justify; text-indent: 15px;">[[w:Abel_Burja|Abel-Burja]] de Leipzig a tâché d’expliquer d’une autre manière la durée du calendrier d’Hipparque (''Astronomisches Jahrbuch'', 1797, pag. 233 et 234). [[w:Claude_Ptolémée|Ptolémée]] et [[w:Censorin_(grammairien)|Censorin]] racontent qu’Hipparque est auteur d’une période soli-lunaire de trois cent quatre années solaires. En la prenant deux fois, on obtient une période de six cent huit ans. Celle-ci fut abrégée par Hipparque de huit ans, afin d’obtenir un nombre entier de siècles pour son calendrier. Ideler a fait une objection très-juste contre cette opinion de Burja ; c’est que la période de six cent huit ans n’a aucun avantage sur celle de trois cent quatre ans. On ne voit donc pas ce qui a pu engager Hipparque à préférer le nombre de six cents ans à celui de trois cents, lorsqu’il composait son calendrier.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Menses_gentium_etc_AdG_back|<span id="Menses_gentium_etc_AdG"><sup>7</sup></span>]] C’est-à-dire il écrivit des éphémérides dans lesquelles il avait calculé d’avance les néomenies et les pleines lunes. Il fit aussi entrer dans son calendrier les longueurs des jours et des heures variables, [[w:ὧραι|ὧραι]] καιρικαι, ainsi que les aspects du ciel, ''visus populorum'', tels qu’ils eurent lieu chez les habitans de différentes contrées de la terre. Il ajouta une table des longitudes et des latitudes des principaux pays et villes du globe. Ptolémée (''Géogr.'', I, ch. 4) en dit autant d’Hipparque.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Aevo_teste_AdG_back|<span id="Aevo_teste_AdG"><sup>8</sup></span>]] Les tables d’Hipparque étaient dressées pour six cents ans. Cet astronome florissait vers cent cinquante ans avant J.-C. Ainsi, du temps de Pline, on avait encore à jouir de ces tables pour quatre cents ans environ. POINSINET.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Numinum_AdG_back|<span id="Numinum_AdG"><sup>9</sup></span>]] Pline donne souvent l’épithète de divinités aux planètes, à la lune, au soleil, à la terre et aux étoiles fixes.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#In_defectibus_scelera_etc_AdG_back|<span id="In_defectibus_scelera_etc_AdG"><sup>10</sup></span>]] Nous transcrivons ici ce beau passage de l’Uranographie de M. [[w:Louis-Benjamin_Francœur|Francoeur]], qui mérite d’être mis en parallèle avec celui de Pline pour l’élégance du style et les pensées, et qui renferme le meilleur commentaire que nous puissions donner de tout ce que Pline dit des terreurs que les éclipses causaient autrefois aux hommes ignorans. « L’histoire, dit M. Francoeur (page 93), est pleine des exemples de l’effroi causé par les éclipses, et des dangers que produisent l’ignorance et la superstition. Nicias avait résolu de quitter la Sicile avec son armée ; effrayé par une éclipse de lune, et voulant temporiser plusieurs jours pour s’assurer si l’astre n’avait rien perdu après cet évènenement, il manqua ainsi l’occasion de sa retraite; son armée fut détruite ; Nicias périt, et ce malheur commença la ruine d’Athènes.<br /><p style="text-align: justify; text-indent: 15px;">« Souvent on a vu des hommes adroits tirer parti de la frayeur du peuple pour l’amener à remplir leurs desseins. Christophe Colomb, réduit à faire subsister ses soldats des dons volontaires d’une nation sauvage et indigente, était prêt à voir tarir cette ressource et à périr de faim ; il annonce qu’il va priver le monde de la lumière de la lune. L’éclipse commence et la terreur s’empare des Indiens, qui reviennent apporter aux pieds de Colomb les tributs accoutumés.<br /><p style="text-align: justify; text-indent: 15px;">« Drusus (TACITE , Annales, I, 28) apaisa une sédition dans son armée, en prédisant une éclipse de lune, et, selon Tite-Live, Sulpicius Gallus, dans la guerre de Paul-Emile contre Persée, usa du même stratagème. Periclès, [[w:Agathocle_de_Syracuse|Agathocles de Syracuse]], [[w:Dion_de_Syracuse|Dion]], roi de Sicile, ont failli être victimes de l’ignorance de leurs soldats. [[w:Alexandre_le_Grand|Alexandre]], près d’[[w:Bataille_de_Gaugamèles|Arbelles]], est réduit à user de toute son adresse pour calmer la terreur qu’une éclipse avait jetée parmi ses troupes. Les hommes supérieurs, plutôt que de plier sous les circonstances qui les maîtrisent, mettent leur art à les tourner à leur profit.<br /><p style="text-align: justify; text-indent: 15px;">« Combien de fables établies d’après l’opinion que les éclipses sont l’effet du courroux céleste qui se venge des iniquités de l’homme en le privant de la lumière! Tantôt [[w:Diane_(mythologie)|Diane]] va trouver [[w:Endymion|Endymion]] dans les montagnes de Carie; tantôt les magiciennes de Thessalie font descendre la lune sur les herbes qu’elles destinent aux enchantemens. »<br /><br /><p style="text-align: center;">''Carmina vel cælo possunt deducere lunam.''<br /><p style="text-align: right; text-indent: 15px;">Virg., ''Eclog.'' VIII.<br /><br /><p style="text-align: justify; text-indent: 15px;">« Ici c’est un dragon qui dévore l’astre, et qu’on cherche à épouvanter par des cris ; le dieu tient le soleil enfermé dans un tuyau, et nous ôte ou nous rend la vue de cet astre à l’aide d’un volet, etc. Les progrès des sciences ont fait connaître le ridicule de ces opinions et de ces craintes, depuis qu’on a vu qu’il était possible de calculer par les tables astronomiques, et de prévoir long-temps d’avance l’instant où la colère du ciel devait éclater.<br /><p style="text-align: justify; text-indent: 15px;">« Cependant, naguère encore, l’épouvante a causé les revers des armées de Louis XIV, près de Barcelone, lors de l’éclipse totale de l’an 1706 [[w:en:Solar_eclipse_of_May_12,_1706|(en)]], et la devise, ''nec pluribus impar'', a prêté aux allusions injurieuses ! »''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Pindari_AdG_back|<span id="Pindari_AdG"><sup>11</sup></span>]] Pindare était le plus fameux poète de la Grèce après Homère. Il vint au monde l’an 134 avant l’ère chrétienne. POINSINET.<br /><p style="text-align: justify; text-indent: 15px;">Plutarque (''De la face de la lune'', pag. 931) dit aussi que Stésichore et Pindare craignaient beaucoup les éclipses. Le dernier poète a peint les terreurs que lui causaient ces phénomènes dans son poëme sur le soleil. HARDOUIN et DALECHAMP.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Crepitu_dissono_AdG_back|<span id="Crepitu_dissono_AdG"><sup>12</sup></span>]] Cet usage superstitieux dont Plutarque parle au long dans sa vie de Paul-Émile, a fourni un vers fort plaisant à Juvénal, lorsqu’après avoir épuisé toute sorte d’exagération pour représenter le bruit qu’une femme fait en criant, il finit par dire :<br /><br /><p style="text-align: center;">''Una laboranti poterit succurrere lunæ.''<br /><br /><p style="text-align: justify; text-indent: 15px;">« Elle seule, au besoin, décharmerait la lune. » POINSINET.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Nicias_AdG_back|<span id="Nicias_AdG"><sup>13</sup></span>]] Le même fait est raconté par Plutarque dans la vie de Nicias, par Quintilien, I, 10, et par d’autres écrivains anciens. HARDOUIN.''' </td> </tr> </table><br/><br/></div>{{Boîte déroulante fin}} <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k5773334c/f10.item <u>Histoire Naturelle de Pline. Tome Second</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k5773334c/f14.item ''Livre II.''], [https://gallica.bnf.fr/ark:/12148/bpt6k5773334c/f46.item ''chap. IX.''], traduction nouvelle par M. [[w:Stéphane_Ajasson_de_Grandsagne|Ajasson de Grandsagne]] [https://gallica.bnf.fr/ark:/12148/bpt6k5773334c/f311.item <sup>NOTES</sup>], C. L. F. Panckoucke, Paris, 1829</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: center; margin: 0 2em;">''Des inventions a[[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki />tronomiques, & de leurs Auteurs.''<br /><p style="text-align: justify; text-indent: 15px;">'''L'''E PREMIER d’entre les ''Romains'' qui rendit publique la théorie des éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oleil & de lune, fut '''Sulpicius Gallus''' [[#Sulpicius_Gallus_LPdS|<span id="Sulpicius_Gallus_LPdS_back"><sup>'''1'''</sup></span>]], celui que '''Marcus Marcellus''' eut pour Collegue au Con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ulat : mais il n’étoit que Tribun Militaire [[#Tribun_Militaire_LPdS|<span id="Tribun_Militaire_LPdS_back"><sup>'''2'''</sup></span>]], lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’il di{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ipa l’allarme qu’auroient pu prendre nos troupes la veille de la victoire remportée {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur '''Per{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ée''' par '''Paul Emile''' ; car ce Général l’ayant produit devant les {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oldats a{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}emblés, il leur prédit une éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e qui devoit arriver [[#éclipse_LPdS|<span id="éclipse_LPdS_back"><sup>'''3'''</sup></span>]] ; il compo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}a même en{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uite un Ouvrage {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur ce {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ujet. Parmi les ''Grecs'', '''Thalès''' de ''Milet'' prédit l’an quatrieme de la quarante-huitieme olympiade l’éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oleil qui arriva {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le regne de '''Halyattes''' [[#Halyattes_LPdS|<span id="Halyattes_LPdS_back"><sup>'''4'''</sup></span>]], l’an cent {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oixante de la fondation de [[#cent_soixante_LPdS|<span id="cent_soixante_LPdS_back"><sup>'''5'''</sup></span>]] ''Rome''. Après eux, Hipparque [[#Hipparque_LPdS|<span id="Hipparque_LPdS_back"><sup>'''6'''</sup></span>]] dre{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}a des Tables en vers [[#En_vers_LPdS|<span id="En_vers_LPdS_back"><sup>'''7'''</sup></span>]] du cours de ces deux aftres pour fix cents ans. Dans ces Tables , de l’exactitude deſquelles notre âge rend encore témoignage [[#témoignage_LPdS|<span id="témoignage_LPdS_back"><sup>'''8'''</sup></span>]], il embra{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}e les éphémérides propres à chaque nation [[#chaque_nation_LPdS|<span id="chaque_nation_LPdS_back"><sup>'''9'''</sup></span>]], les jours, les heures, le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ite re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pectif de chaque lieu, & les divers a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pects du ciel relativement aux divers peuples, comme {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i la Nature l’eût admis à {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}eil intime. Per{{Info|ſ|forme ancienne longue de la lettre s minuscule}}onnages vraiment grands! génies plus qu’humains, d’avoir ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i {{Info|ſ|forme ancienne longue de la lettre s minuscule}}urpris les loix qui font mouvoir ces va{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tes pui{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ances du ciel ; & d’avoir guéri de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}es allarmes l’imagination malade des hommes, qui ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’alors, ou avoient toujours vu dans les éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es l’annonce effrayante de quelque grand crime & de quelque mort (terreur dont Sté{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ikhore [[#Stésikhore_LPdS|<span id="Stésikhore_LPdS_back"><sup>'''10'''</sup></span>]] & Pindare [[#Pindare_LPdS|<span id="Pindare_LPdS_back"><sup>'''11'''</sup></span>]], ces Poètes {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ublimes, ne furent point exempts à l’égard des éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es {{Info|ſ|forme ancienne longue de la lettre s minuscule}}olaires), ou attribuoient les ténebres dont {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e couvre la lune à des maléfices opérés par le mêlange de certaines herbes magiques ; & croyoient devoir la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ecourir par un bruit di{{Info|ſ|forme ancienne longue de la lettre s minuscule}}cordant [[#bruit_discordant_LPdS|<span id="bruit_discordant_LPdS_back"><sup>'''12'''</sup></span>]]. Cette même terreur fut cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e que le Général Nicias [[#Nicias_LPdS|<span id="Nicias_LPdS_back"><sup>'''13'''</sup></span>]], peu au fait des cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es phy{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iques, & n’o{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ant pas, par {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uper{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tition, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortir {{Info|ſ|forme ancienne longue de la lettre s minuscule}}a flotte du Port, mit Athenes à deux doigts de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}a perte. Honneur [[#Honneur_LPdS|<span id="Honneur_LPdS_back"><sup>'''14'''</sup></span>]] à vous, Interpretes du Ciel ! E{{Info|ſ|forme ancienne longue de la lettre s minuscule}}prits [[#Esprits_LPdS|<span id="Esprits_LPdS_back"><sup>'''15'''</sup></span>]] dont l’étendue {{Info|ſ|forme ancienne longue de la lettre s minuscule}}urpa{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}e celle de la Nature ; Inventeurs d’une méthode qui a{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ujettit les dieux comme les hommes, à une même de{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tinée ! Eh! qui pourroit, en voyant les a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres même ''en cri{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e'' (pour me {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervir de l’expre{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ion commune), ne pas {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oumettre à la néce{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ité où {{Info|ſ|forme ancienne longue de la lettre s minuscule}}a condition mortelle l’enchaîne.<br /><p style="text-align: justify; text-indent: 15px;">Pré{{Info|ſ|forme ancienne longue de la lettre s minuscule}}entement je vais toucher par articles fort courts & fort précis les points {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le{{Info|ſ|forme ancienne longue de la lettre s minuscule}}quels on s’accorde le plus. Je ré{{Info|ſ|forme ancienne longue de la lettre s minuscule}}oudrai, chemin fai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ant, quelques que{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tions, lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que le cas l’exigera, mais toujours d’une maniere très {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ommaire ; car une analy{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e en forme d’arguments {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivis excéderoit le but de cet Ouvrage : & puis, je pen{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e qu’il n’y a pas moins de mérite à rendre une rai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}on plau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ible de toutes cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es, qu’à rendre une rai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}on bien {{Info|ſ|forme ancienne longue de la lettre s minuscule}}olide de deux ou trois cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement.</div> {{Boîte déroulante début|titre=Notes du traducteur|alignT=center}} <div style="text-align: justify; border: 2px; border-radius:15px; font-size:85%;"><br/> <table cellspacing=15 align=center style="margin: 0 4em;"> <tr> <td style="text-align: justify; margin: 0 4em; text-indent: 15px">'''[[#Sulpicius_Gallus_LPdS_back|<span id="Sulpicius_Gallus_LPdS"><sup>1</sup></span>]] Son premier prénom étoit Caïus. Voyez à {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ujet Tite-Live, l. 44. Valere maxime, l. 8. Quintilien, l. I. ch. 10. Plutarque, vie de Paul-Emile, &c.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Tribun_Militaire_LPdS_back|<span id="Tribun_Militaire_LPdS"><sup>2</sup></span>]] Ce grade répond à celui de Major-général des Troupes.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#éclipse_LPdS_back|<span id="éclipse_LPdS"><sup>3</sup></span>]] Cette éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon Tite-Live, fut annoncée aux {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oldats pour la nuit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivante. Sulpicius Gallus leur prédit que la lune s’éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit entre la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}econde heure de la nuit & la quatrieme. Plutarque ajoute qu’on étoit alors à l’i{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ue de l’été (''{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ub exitum a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tatis''). [[w:Paulin_II_d'Aquilée|Paul d’Aquilée]] écrit que cette éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e arriva aux nones de Septembre (''nonas Septembris''), c’est-à-dire au 4 Septembre : c’étoit l’an 168 avant J.C. {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon le calcul d’[[w:James_Ussher|U{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}erius]].''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Halyattes_LPdS_back|<span id="Halyattes_LPdS"><sup>4</sup></span>]] Il paroît que Ciceron & Eu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ebe {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e trompent lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’ils placent cet événement {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous ''A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tiages'' : [[w:Hermolaos_(Macédoine)|Hermolaüs]] s’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t trompé d’après eux, en {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ub{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tituant ''A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tiages'' à ''Halyattes'' dans le texte de Pline, contre la foi des manu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}crits, & contre le témoignage d’Herodote qui place cet événement dans une guerre entre Halyattes, Roi de Sardes, & Cyaxare, Roi des Medes, pere d’A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tiages.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#cent_soixante_LPdS_back|<span id="cent_soixante_LPdS"><sup>5</sup></span>]] Les deux manu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}crits royaux portent ''anno CLX'' : c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t la leçon qu’il faut {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivre. En effet, Rome, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon Varron, fut fondée l’an 2 de la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ixieme olympiade, c’est-à-dire, dans l’année olympiadique 22. Mais comme chaque olympiade, depuis leur premiere in{{Info|ſ|forme ancienne longue de la lettre s minuscule}}titution, commençoit après le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ol{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tice d’été, & comme l’année Romaine avoit toujours commencé au plus tard en Mars {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous Romulus, & depuis en Janvier {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous Numa, il s’en{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uit que la premiere année de Rome, qui répondoit à l’année olympiadique 22, répondoit en même-tems, de quatre mois au moins, à l’année olympíadique 23. Selon ce calcul, la quatrieme année de la quarante-huitieme olympiade, répond en partie à l’an 160, & en partie à l’an 161 de la fondation de Rome, pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que 48 olympiades font cent quatre-vingt-douze années, de{{Info|ſ|forme ancienne longue de la lettre s minuscule}}quelles {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i vous ôtez 22 ans écoulés, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon Varron, entre la premiere olympiade & la fondation de Rome, il re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tera 160 ans, & 161 ans {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i l’on a égard à l’enjambement réciproque des années olympiadiques {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur les années Romaines, & des années Romaines {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur les années olympiadiques. Il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t donc évident que le calcul de Pline (''anno CLX'') e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te, en admettant l’hypothe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e chronologique de Varron, à laquelle on voit bien que notre Auteur s’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t conformé en cette occa{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ion particuliere encore qu’en plu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ieurs autres rencontres il paroi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}e affecter de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivre le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}y{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tême de Caton : vici{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}itude pardonnable dans un ouvrage de compilation où Pline a dû, comme malgré lui, adopter tantôt le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}tyle d’un Auteur, tantôt celui d’un autre, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ource où il pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}oit pour l’heure. Si Pline eût {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivi l’hypothe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e de Caton qui place la fondation de Rome deux ans plus tard que Varron, il eût fait tomber le rapport {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur l’année 157 ou 158 de la fondation de Rome. C’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t le parti que prend le Pere Hardouin, & c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t de là qu’il part pour propo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}er une correction dans le texte. Mais encore une fois, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t commettre {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oi-même une faute que d’en trouver une chez Pline en cette occa{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ion ; pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que cet Auteur a été en droit de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervir du {{Info|ſ|forme ancienne longue de la lettre s minuscule}}y{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tème Varronien, ou ce qui revient au même, de con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}erver la date Varronienne dont s’étoit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervi l’Auteur de qui il emprunte le fait hi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}torique en que{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tion.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Hipparque_LPdS_back|<span id="Hipparque_LPdS"><sup>6</sup></span>]] Voyez les notes {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le premier livre au mot ''Hipparque'' : HIPPARCHUS (de Nicée, en Bithinie comme l’ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}erve [[w:Souda|Suidas]]), flori{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}oit dans la quatre-vingt-quatorzieme olympiade. Nous avons {{Info|ſ|forme ancienne longue de la lettre s minuscule}}es trois livres d’Enarrations {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur les Phénomenes d’[[w:Aratos_de_Soles|Aratus]] & d’[[w:Eudoxe_de_Cnide|Eudoxe]], traduits par le [[w:Paul_Petau|P. Petau]]. Il avoit compo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}é un autre livre ''De {{Info|ſ|forme ancienne longue de la lettre s minuscule}}tellarum inerrantium Con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}titutione'', {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon Suidas. Son livre du mois lunaire e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t cité par [[w:Claude_Galien|Galien]].''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#En_vers_LPdS_back|<span id="En_vers_LPdS"><sup>7</sup></span>]] ''En Vers.'' Je préfume que c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t-là le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ens de ''præcinere'', qui ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ignifie pas {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement prédire, mais prédire en chant, c’est-à-dire en Vers. Sur ce pied-là, ce {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit d’un Poëme dans le genre de celui d’[[w:Aratos_de_Soles|Aratus]], dont il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit ici que{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tion. Si par hazard Pline n’a point prétendu parler d’un Poëme, au-moins s’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t-il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervi d’une expre{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ion propre à dé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}igner ce genre d’écrire. Pour décider la que{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tion, il faudroit avoir tous les ouvrages d’Hipparque, & {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur-tout celui-ci.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#témoignage_LPdS_back|<span id="témoignage_LPdS"><sup>8</sup></span>]] Ces tables étoient dre{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ées pour {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ix cents ans. Or Hipparque flori{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}oit 150 ans avant J.C. Ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i du tems de Pline, on avoit encore à jouir de ces tables pour quatre cents ans ou environ.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#chaque_nation_LPdS_back|<span id="chaque_nation_LPdS"><sup>9</sup></span>]] La lune ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e levant pas & ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e couchant pas à la même heure pour toutes les nations, les diver{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es époques & pha{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es qu’elle forme n’appartiennent pas non plus au même point de tems pour tous les peuples, pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que les uns ont la nuit quand les autres ont le jour; & que même lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que la lune s’éclip{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e, la partie de l’heure où ce phénomene e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t {{Info|ſ|forme ancienne longue de la lettre s minuscule}}en{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ible, n’a pas la même évaluation pour tous les climats qui l’apperçoivent. Hipparque avoit donc eu égard à cette différence d’époques, relativement aux diver{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es longitudes, &par-con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}équent aux diver{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es nations, tellement que les tables de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}es éphémérides étoient accommodées à l’u{{Info|ſ|forme ancienne longue de la lettre s minuscule}}age de tous les peuples : ouvrage dont on ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}auroit trop regretter la perte, & qui jetteroit le plus grand jour {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur l’ancienne géographie.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Stésikhore_LPdS_back|<span id="Stésikhore_LPdS"><sup>10</sup></span>]] Ste{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ikhore, l’un des plus fameux Poètes de la Grece, dont, par malheur, nous avons perdu tous les ouvrages, à l’exception d’une vingtaine de lignes décou{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ues. Il vivoit dans la quarante-deuxieme olympiade, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t-à-dire, vers l’an {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ix cents dix avant J.C.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Pindare_LPdS_back|<span id="Pindare_LPdS"><sup>11</sup></span>]] Pindare, le plus fameux Poète de la Grece après Homere. Il vint au monde l’an 134 avant l’ere chrétienne.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#bruit_discordant_LPdS_back|<span id="bruit_discordant_LPdS"><sup>12</sup></span>]] Cet u{{Info|ſ|forme ancienne longue de la lettre s minuscule}}age {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uper{{Info|ſ|forme ancienne longue de la lettre s minuscule}}titieux a fourni un Vers fort plai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ant à Juvenal, lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’après avoir épui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}é toute {{Info|ſ|forme ancienne longue de la lettre s minuscule}}orte d’exagération pour repré{{Info|ſ|forme ancienne longue de la lettre s minuscule}}enter le bruit qu’une femme fait en criant, il finit par dire :<br /><p style="text-align: justify; text-indent: 15px;">''Una laboranti poterit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uccurrere luna.''<br /><p style="text-align: justify; text-indent: 15px;">Elle {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eule au be{{Info|ſ|forme ancienne longue de la lettre s minuscule}}oin décharmeroit la lune.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Nicias_LPdS_back|<span id="Nicias_LPdS"><sup>13</sup></span>]] C’étoit un Général Athénien, qui fut malheureux dans pre{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que toutes {{Info|ſ|forme ancienne longue de la lettre s minuscule}}es expéditions. Les Athéniens finirent par le condamner à mort. Il étoit contemporain d’[[w:Alcibiade|Alcibiade]] & de [[w:Lamachos|Lamachus]], & leur collegue dans le commandement. Voyez {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le fait dont parle ici Pline, Quintilien, l. I. ch. 10, & Plutarque à l’article Nicias.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Honneur_LPdS_back|<span id="Honneur_LPdS"><sup>14</sup></span>]] Au lieu de ''macte ingenio'', le Pere Hardouin lit ''macti'' ; mais {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon les meilleurs Latini{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tes ''macte'' e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t indéclinable, ou pour mieux dire, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t un adverbe qui répond au ''bravò'' des Italiens. Fe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tus prétend que ''macte'' e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t un compo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}é de ''magis auctus''.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Esprits_LPdS_back|<span id="Esprits_LPdS"><sup>15</sup></span>]] Cette apo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}trophe de Pline aux A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tronomes rappelle ces beaux Vers d'Ovide {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le même {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ujet :<br />Felices animos quibus hæc cogno{{Info|ſ|forme ancienne longue de la lettre s minuscule}}cere primise<br /><p style="text-align: justify; text-indent: 15px;">Inque Domos {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uperas {{Info|ſ|forme ancienne longue de la lettre s minuscule}}candere cura fuit!<br />Credibile e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t illos pariter vitli{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que joci{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que<br /><p style="text-align: justify; text-indent: 15px;">Altius humanis ex{{Info|ſ|forme ancienne longue de la lettre s minuscule}}eruifle caput,<br />Non Venus aut Vinum {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ublimia pectora fregit<br /><p style="text-align: justify; text-indent: 15px;">Officiumve {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ori, militiæve labor :<br />Nec levis ambitio, perfu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}aque gloria fuco,<br /><p style="text-align: justify; text-indent: 15px;">Magnarumve fames {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ollicitavit opum.<br />Admovere oculis di{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tantia {{Info|ſ|forme ancienne longue de la lettre s minuscule}}idera no{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tris,<br /><p style="text-align: justify; text-indent: 15px;">Ætheraque ingenio {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uppo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uere {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uo.<br />Sic petitur cœlum: non ut ferat O{{Info|ſ|forme ancienne longue de la lettre s minuscule}}lan Olympus<br /><p style="text-align: justify; text-indent: 15px;">Summaque Peliacus {{Info|ſ|forme ancienne longue de la lettre s minuscule}}idera tangat apex, &c.<br /><p style="text-align: right; text-indent: 15px;">Ovid. {{Info|ſ|forme ancienne longue de la lettre s minuscule}}a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t. l. I.''' </td> </tr> </table><br/><br/></div> {{Boîte déroulante fin}} <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=UnhluychtF8C&newbks=1&newbks_redir=0&dq=Histoire%20naturelle%20Pline&hl=fr&pg=PP7#v=onepage&q&f=true <u>Histoire Naturelle de Pline. Tome Premier</u>], [https://books.google.fr/books?id=UnhluychtF8C&newbks=1&newbks_redir=0&dq=Histoire%20naturelle%20Pline&hl=fr&pg=RA1-PA3#v=onepage&q&f=true ''Livre Second.''], [https://books.google.fr/books?id=UnhluychtF8C&newbks=1&newbks_redir=0&dq=Histoire%20naturelle%20Pline&hl=fr&pg=RA1-PA59#v=onepage&q&f=true ''Des inventions astronomiques, & de leurs Auteurs.''], traduction en françois, avec le texte latin rétabli d’après les meilleures leçons manu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}crites ; accompagnée de Notes critiques pour l’éclairci{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ement du texte, & d’Ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervations {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur les connoi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ances des Anciens comparées avec les découvertes des Modernes, par M. [[w:Louis_Poinsinet_de_Sivry|Louis Poinsinet de Sivry]], Chez la veuve Desaint, Paris, 1771</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div> </div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ==== Livre XVIII ==== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Enquête sur l’agriculture</div> ===== <div style="text-align: center;">Chapitre LVII.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage d’une théorie astronomique de '''Thalès''', du [[w:Lever_héliaque|''coucher matinal'']] des [[w:Pléiades_(astronomie)|''Pléiades'']] 25 jours après l’[[w:Équinoxe_de_septembre|''équinoxe d’automne'']].</div> :'''Texte latin''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''LVII.''' Primum omnium dierum ipsorum anni solisque motus prope inexplicabilis ratio est. Ad CCCLXV adjiciunt etiamnum intercalarios diei noctisque quadrantes. Ita fit, ut tradi non possint certa siderum tempora. Accedit confessa rerum obscuritas, nunc præcurrenle, nec paucis diebus, tempestatum significatu, quod προϰειμασιν Græci vocant : nunc postveniente, quod ἐπιϰεἰμασιν : et plerumque alias citius, alias tardius cæli effectu ad terram deciduo : vulgo serenitate reddita, confectum sidus audimus. Præterea quum omnia hæc statis sideribus cæloque affixis constent, interveniunt motu stellarum grandines, imbres, et ipsi non levi effectu, ut docuimus, turbantque conceptæ spei ordinem. ldque ne nobis tantum putemus accidere, et reliqua fallit animalia sagaciora circa hoc, ut quo vita eorum constet : æstivasque alites præposteri aut præproperi rigores necant, hibernas æstus. Ideo '''Virgilius''' errantium quoque siderum rationem ediscendam præcipit, admonens observandum frigidæ Saturni stellæ transitum. Sunt qui certissimum veris indicium arbitrentur ob infirmitatem animalis, papilionis proventum. Id eo ipso anno, quum commentaremur hæc, notatum est, proventum eorum ter repetito frigore exstinctum, advenasque volucres a. d. VI kalendas februarii spem veris attulisse, mox sævissima hieme conflictatas. Res anceps : primum omnium a cælo peti legem : deinde eam argumentis esse quærendam. Super omnia est mundi convexitas, terrarumque globi differentia, eodem sidere alio tempore aliis aperiente se gentibus : quo fit ut causa ejus non lisdem diebus ubique valeat. Addidere difficultatem et auclores diversis in locis observando, mox etiam in iisdem diversa prodendo. Très autem fuere sectæ : ''Chaldæa'', ''Ægyptia'', ''Græca''. His addidit apud nos quartam Cæsar dictator, annos ad solis cursum redigens singulos, '''Sosigene''' perito scientiæ ejus adhibito. Et ea ipsa ratio postea comperto errore correcta est : ita ut XII annis continuis non intercalaretur, quia cœperat sidera annus morari, qui prius antecedebat. Et '''Sosigenes''' ipse trinis commentationibus, quanquam diligentior cæteris, non cessavit tamen addubitare, ipse semet corrigendo. Auctores prodidere ea, quos prætexuimus volumini huic, raro ullius sententia cum alio congruente. Minus hoc in reliquis mirum, quos diversi excusaverint tractus. Eorum qui in eadem regione dissedere, unam discordiam ponemus exempli gratia : occasum matutinum Vergiliarum '''Hesiodus''' (nam hujus quoque nomine exstat Astrologia) tradidit fieri, quum æquinoctium autumni conficeretur, '''Thales''' vigesimo quinto die ab æquinoctio, '''Anaximander''' vigesimo nono, '''Euctemon''' XLVIII. Nos sequemur observationem Cæsaris : maximeque hæc erit ''Italiæ'' ratio. Dicemus tamen et aliorum placita : quoniam non unius terræ, sed totius naturæ interpretes sumus, non auctoribus positis (id enim verbosum est), sed regionibus : legentes tantum meminerint, brevitatis gratia, quum ''Altica'' nominata fuerit, simul intelligere ''Cycladas'' insulas ; quum ''Macedonia'', ''Magnesiam'', ''Thraciam'' ; quum ''Ægyptus'', ''Phœnicen'', ''Cyprum'', ''Ciliciam'' ; quum ''Bœotia'', ''Locridem'', ''Phocidem'', et finitimos semper tractus ; quum ''Hellespontus'', ''Cherronesum'', et continentia usque ''Atho'' montem ; quum ''Ionia'', ''Asiam'', et insulas ''Asiæ'' ; quum ''Peloponnesus'', ''Achaiam'', et ad ''Hesperum'' jacentes terras. ''Chaldæi Assyriam'' et ''Babyloniam'' demonstrabunt. ''Africam'', ''Hispanias'', ''Gallias'' sileri non erit mirum. Nemo enim observavit in iis, qui siderum proderet exortus. Non tamen difficili ratione dignoscentur in illis quoque terris digestione circulorum, quam in sexto volumine fecimus : qua cognatio cæli, non gentium modo, verum urbium quoque singularum intelligitur, nota ex his terris, quas nominavimus, sumta convexitate circuli, pertinentis ad quas quisque quæret terras, et ad earum siderum exortus, per omnium circulorum pares umbras. Indicandum et illud, tempestates ipsas ardores suos habere quadrinis annis : et easdem non magna differentia reverti ratione solis : octonis vero augeri easdem, centesima revolvente se luna.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/b24875958_0001/page/n7/mode/2up <u>Histoire Naturelle de Pline.</u>], [https://archive.org/details/b24875958_0001/page/652/mode/2up ''Livre XVIII.''], [https://archive.org/details/b24875958_0001/page/684/mode/2up ''Chap. LVII.''], texte corrigé par [[w:Émile_Littré|M. É. Littré]], à partir de celui de [[w:Jean_Hardouin|Hardouin]], Librairie de Firmin-Didot et C<sup>ie</sup>, Paris, 1883<br />(également disponible une édition de 1831 [https://gallica.bnf.fr/ark:/12148/bpt6k5804072n/f339.item ici] et de 1848 [[s:Page:Pline_l'ancien_-_Histoire_naturelle,_Littré,_T1_-_1848.djvu/708|là]])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''LVII.''' Avant tout, le calcul des jours même de l’année et du mouvement solaire est d’une difficulté presque insurmontable. Aux trois cent soixante-cinq jours on ajoute des jours intercalaires, produits de quarts de jour et de nuit ; de là vient qu’on ne peut indiquer des époques fixes pour les astres. Ajoutez une obscurité des choses avouée de tous : tantôt en effet la mauvaise saison, s’annonçant, anticipe même de plusieurs jours, ce que les Grecs appellent προϰεἰμασις (avant-hiver), et la belle saison retarde, ce qui est nommé ἐπιϰεἰμασις (arriere-hiver) : l’effet du ciel tombe sur la terre tantôt plus vite, tantôt plus tardivement ; et d’ordinaire c’est quand la sérénité est rétablie que nous entendons dire que l’action de l’astre est accomplie. En outre, car tous ces phénomènes dépendent d’astres réglés et fixés au ciel, le mouvement des étoiles amène intercurremment des grêles, des pluies qui ne sont pas non plus d’une faible action, comme nous l’avons enseigné (XVII, 2), et qui troublent l’ordre espéré. Et ne pensons pas que ces méprises n’arrivent qu’à nous; les autres animaux s’y trompent, bien que plus sagaces que nous sur ce point, vu que leur vie en dépend : l’on voit les oiseaux d’été tués par des froids hâtifs ou tardifs, et les oiseaux d’hiver par des chaleurs hâtives ou tardives. Aussi [[w:Virgile|'''Virgile''']] [[#Virgile|<span id="Virgile_back"><sup>'''I'''</sup></span>]] ([[w:Géorgiques|''Georg.'']], I, 335) recommande-t-il d’étudier encore le cours des astres errants, avertissant d’observer le passage de Saturne, planète froide. Il en est qui regardent comme l’indice le plus sûr du printemps l’apparition des papillons, à cause de la délicatesse de cet insecte. Or, l’année même où nous écrivions ceci (an 830 de ''Rome''), il a été noté que les papillons, ayant éclos, furent détruits à trois reprises par le froid, et que les oiseaux étrangers, ayant apporté l’espérance du printemps avant le 6 des calendes de février (27 janvier), eurent bientôt après à essuyer un hiver très-rigoureux. La double difficulté est d’abord d’avoir à demander au ciel la règle de toute chose, puis d’être obligé de contrôler cette règle par des faits apparents. Avant tout signalons la convexité du monde et les différences du globe terrestre, qui font que le même astre se montre à des temps divers suivant les nations, de sorte que l’influence ne s’en fait pas sentir partout aux mêmes jours. La difficulté a été encore accrue par les auteurs qui ont observé en des lieux différents, ou même qui, ayant observé dans les mêmes lieux, ont publié des résultats divergents. Il y a eu trois écoles, la ''Chaldéenne'', l’Égyptienne, la ''Grecque''. Une quatrième a été formée chez nous par le dictateur [[w:Jules_César|'''César''']], qui ramena l’année à la révolution solaire avec l’aide de [[w:Sosigène_d'Alexandrie|'''Sosigène''']] [[#Sosigène|<span id="Sosigène_back"><sup>'''II'''</sup></span>]], astronome habile. Et ce calcul même, où l’on découvrit une erreur, a été corrigé : pendant douze années consécutives on ne fit pas d’intercalation, attendu que l’année, qui auparavant anticipait, maintenant retardait sur les astres. '''Sosigène''' lui-même, quoique plus exact que les autres, n’a pas cessé, dans trois mémoires, de témoigner de ses doutes en se corrigeant lui-même. Les auteurs que nous avons indiqués au commencement de ce livre [[#auteurs_JH|<span id="auteurs_JH_back"><sup>'''1'''</sup></span>]] ont révélé ces discordances, l’avis de l’un s’accordant rarement avec l’avis de l’autre. Cela est moins étonnant dans ceux qui s’excuseront par la différence des lieux. Parmi ceux qui dans le même pays sont en désaccord, nous choisirons un exemple de dissidence : [[w:Hésiode|'''Hésiode''']] [[#Hésiode|<span id="Hésiode_back"><sup>'''III'''</sup></span>]] (car nous avons aussi sous son nom un livre sur les astres) a rapporté que le [[w:Lever_héliaque|''coucher matinal'']] des [[w:Pléiades_(astronomie)|''Pléiades'']] se faisait au moment de l’[[w:Équinoxe_de_septembre|''équinoxe d’automne'']] ; '''Thalès''', qu’il se faisait vingt-cinq jours après cet équinoxe; [[w:Anaximandre|'''Anaximandre''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Anaximandre_back|<sup>🔄</sup>]], vingt-neuf; [[w:Euctémon|'''Euctémon''']] [[#Euctémon|<span id="Euctémon_back"><sup>'''IV'''</sup></span>]], quarante-huit. Quant à nous, nous suivrons les calculs de '''César''' : ils se rapportent spécialement à l’Italie. Toutefois, nous relaterons aussi les opinions des autres ; car nous sommes les interprètes, non d’un seul pays, mais de la nature entière. Nous nommerons, non pas les auteurs, ce qui serait trop long, mais les pays. Les lecteurs auront seulement à se souvenir que, pour abréger, sous le nom d’[[w:Attique|''Attique'']] nous entendons aussi les [[w:Cyclades|''Cyclades'']]; sous celui de [[w:Macédoine_(province_romaine)|''Macédoine'']], la [[w:Magnésie_antique|''Magnésie'']] et la [[w:Thrace_(province_romaine)|''Thrace'']]; sous celui d’[[w:Égypte_romaine_et_byzantine|''Égypte'']], la [[w:Syrie-Phénicie_(province_romaine)|''Phénicie'']], [[w:Chypre_(province_romaine)|''Chypre'']] et la [[w:Cilicie|''Cilicie'']]; sous celui de [[w:Béotie#Antiquité|''Béotie'']], la [[w:Locride_(Grèce)|''Locride'']], la [[w:Phocide#Antiquité_et_période_byzantine|''Phocide'']] et les contrées limitrophes ; sous celui d’[[w:Hellespontique|''Hellespont'']], la [[w:Chersonèse_(cité_grecque)|''Chersonèse'']] et le continent jusqu’au [[w:Mont_Athos|''mont Athos'']]; sous celui d’[[w:Ionie|''Ionie'']], l’Asie et les îles ''Asiatiques'' ; sous celui de [[w:Péloponnèse#Antiquité|''Péloponnèse'']], l’[[w:Achaïe|''Achaïe'']] et les terres situées à l’occident; la [[w:Chaldée|''Chaldée'']] indiquera la [[w:Histoire_de_la_Syrie#Antiquité|''Syrie'']] et la [[w:Babylone_(civilisation)|''Babylonie'']]. On ne s’étonnera pas que je passe sous silence l’[[w:Afrique_romaine|''Afrique'']], l’[[w:Hispanie_romaine|''Espagne'']] et les [[w:Gaule|''Gaules'']], car personne dans ces contrées n’a laissé d’observations sur le lever des astres. Toutefois, il ne sera pas difficile de le calculer, même dans ces contrées, en étudiant la disposition des cercles que nous avons présentés dans le sixième livre (VI, 39). Grâce à cette étude, on connaît les relations astronomiques nou-seulement des nations, mais encore des villes en particulier : étant donnés les cercles déterminés par l’égalité des ombres, on choisit, dans les terres que nous avons nommées, le cercle qui a rapport à la localité objet du problème, et qui détermine en même temps le lever des astres pour cette localité. Il faut encore remarquer (II, 48) que tous les quatre ans les saisons ont leurs excès, et qu’elles reviennent les mêmes sans grande différence, en raison du soleil ; mais que tous les huit ans elles ont un redoublement, à la révolution de la centième lune.</div> <table cellspacing=15 align=center style="margin: 0 4em; font-size:85%;"> <tr> <td style="text-align: justify; margin: 0 4em;">'''[[#auteurs_JH_back|<span id="auteurs_JH"><sup>1</sup></span>]] Ce passage porte à croire que les auteurs dont Pline s’était servi pour composer chacun des livres de son ouvrage avaient été placés en tête du livre auquel ils se rapportaient. Les éditions mettent cette liste d’auteurs à la suite de la table de chaque livre, dans la table générale dressée par Pline lui-même.'''</td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/b24875958_0001/page/n7/mode/2up <u>Histoire Naturelle de Pline.</u>], [https://archive.org/details/b24875958_0001/page/652/mode/2up ''Livre XVIII.''], [https://archive.org/details/b24875958_0001/page/684/mode/2up ''Chap. LVII.''], texte corrigé par [[w:Émile_Littré|M. É. Littré]], à partir de celui de [[w:Jean_Hardouin|Hardouin]] [https://archive.org/details/b24875958_0001/page/708/mode/2up <sup>NOTES</sup>], Librairie de Firmin-Didot et C<sup>ie</sup>, Paris, 1883<br />(également disponible une édition 1848 [[s:Page:Pline_l'ancien_-_Histoire_naturelle,_Littré,_T1_-_1848.djvu/708|ici]])</div> {{Boîte déroulante début|titre=NdA de trad. Jean Hardouin 1883|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Virgile_back|<span id="Virgile"><sup>I</sup></span>]] [[w:Nom_romain|Tria Nomina]] en latin {{Info|Publius|praenomen, nom individuel du citoyen romain}} {{Info|Vergilius|nomen, nom de famille}} {{Info|Maro|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}};<br /><p style="margin: 0 2em; text-indent: 15px">Poète latin renommé dans les styles [[w:Épopée|''épique'']] (l’[[w:Énéide|''Énéide'']]), [[w:Poésie_pastorale|''pastorale'']] (les [[w:Bucoliques|''Bucoliques'']]) et [[w:Poésie_didactique|''didactique'']] (les [[w:Géorgiques|''Géorgiques'']]).<br /><p style="text-align: right; margin: 0 2em;">(15 octobre [[w:Années_70_av._J.-C.|-70]], [[w:Virgilio_(Lombardie)#Histoire|''Andes'']], au sud-est de l’actuelle [[w:Lombardie#Histoire|''Lombardie'']], au nord de l’Italie — 21 septembre [[w:Années_19_av._J.-C.|-19]], [[w:Brindisi#Histoire|''Brundisium'']], au sud-est des actuelles [[w:Pouilles#Domination_romaine|''Pouilles'']], au sud-est de l’Italie)<sup>[[w:Ier_siècle_av._J.-C.|⏳]]</sup> <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Sosigène_back|<span id="Sosigène"><sup>II</sup></span>]] Du nom propre grec ancien Σωσῐγένης / Sōsigénēs [[wikt:en:Σωσιγένης#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">➥ du verbe σῴζω / sṓizō, « 1. Sauver : • Guérir ; • (rare chez Homère) Garder en sécurité, préserver ; • Garder, observer, maintenir ; • (généralement au milieu) Garder à l’esprit, se souvenir ; • Conduire en toute sécurité (à) ; • Secourir ; • Conserver pour. »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ + du nom commun‎ γένος / génos [[wikt:en:γένος#Ancient_Greek|(en)]], « 1. Race, souche, parenté : Descendance directe, par opposition à une relation collatérale. 2. Progéniture, descendant : (collectif) progéniture, postérité. 3. (en général) Race d’êtres : • Famille, clan, maison ; • Tribu, nation, race, en tant que subdivision de ἔθνος / éthnos [[wikt:en:ἔθνος#Ancient_Greek|(en)]] ; • Caste ; • Race d’animaux. 4. Âge, génération, période de la vie. 5. Sexe, genre : (grammaire) Genre grammatical. 6. Classe, sorte, genre : • (logique) L’opposé de εἶδος / eîdos [[wikt:en:εἶδος#Ancient_Greek|(en)]] ; • (taxonomie) Classe : • (taxonomie) Genre ; • Espèce de plante ; culture, produit ; matériau ; • Élément. »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ +‎ du suffixe nominal propre -ης / -ēs [[wikt:en:-ης#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;"> Astronome grec, connu pour avoir participer à la conception du [[w:Calendrier_julien|''calendrier julien'']] (instauré par Jules César en [[w:Années_46_av._J.-C.|-46]]/[[w:Années_45_av._J.-C.|-45]], lorsqu’il était [[w:Pontifex_maximus|''pontifex maximus'']]), avec une année commune de 365 jours divisée en 12 mois, et un jour intercalaire ajouté tous les 4 ans, lors des années bissextiles.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:Ier_siècle_av._J.-C.|I<sup>er</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]]) <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Hésiode_back|<span id="Hésiode"><sup>III</sup></span>]] Du nom propre grec ancien Ἡσῐ́οδος / Hēsíodos;<br /><p style="margin: 0 2em; text-indent: 15px">Poète ''grec'', renommé pour 2 ouvrages :<br /><p style="margin: 0 2em; text-indent: 15px;">• [[w:Théogonie_(Hésiode)|''la Théogonie'']], une généalogie des dieux (dans laquelle il présente la multitude des dieux célébrés par les mythes grecs où trois générations divines se succèdent : celle d’Ouranos, celle de Cronos, celle de Zeus qui sort triomphant) et une cosmogonie (qui retrace la création du monde à partir du Chaos) ;<br /><p style="margin: 0 2em; text-indent: 15px;">• et [[w:Les_Travaux_et_les_Jours|''les Travaux et les Jours'']], un almanac sur l’agriculture à destination de son frère Perses.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:VIIIe_siècle_av._J.-C.|VIII<sup>ème</sup>]] — [[w:VIIe_siècle_av._J.-C.|VII<sup>ème</sup>]] siècles {{Info|AEC|Avant l’Ère Commune}}) <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Euctémon_back|<span id="Euctémon"><sup>IV</sup></span>]] Du nom propre grec ancien Ευκτήμων / Euktémōn;<br /><p style="margin: 0 2em; text-indent: 15px;">Astronome ''athénien'', contemporain et collègue de l’astronome [[w:Méton|Méton]], avec qui, il a fait une série d’observations des [[w:Solstice|''solstices'']] afin de déterminer la durée de l’[[w:Année_tropique|''année tropique'']] [https://ecliptiqc.ca/Almageste_Livre3.php#III1 <sup>Ptolémée, Almageste, liv. III, chap. 1</sup>].<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:Ve_siècle_av._J.-C.|V<sup>ème</sup>]] siècle {{Info|AEC|Avant l’Ère Commune}})'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: center; margin: 0 2em;">Des découvertes astronomiques : part de chaque observateur dans la science.<br /><p style="text-align: justify; text-indent: 15px;">'''LVII.''' D’abord, il est presque impossible de déterminer d’une manière précise le nombre des jours de l’année et le cours du soleil [[#Primum_omnium_dierum_AdG|<span id="Primum_omnium_dierum_AdG_back"><sup>'''1'''</sup></span>]]. Aux trois cent soixante-cinq jours qui composent l’année, on ajoute le quart d’un jour et d’une nuit, pour en faire ensuite un jour intercalaire ; de là il suit qu’on ne saurait indiquer avec précision le moment du lever et du coucher des astres. On convient qu’il y a encore dans cette théorie beaucoup d’obscurité ; en effet, les saisons quelquefois commencent plusieurs jours avant le terme qui leur a été fixé, ce que les ''Grecs'' appellent ''procheimasis'' ; d’autres fois, plusieurs jours après, ce qu’ils appellent ''épicheimasis'' [[#Accedit_confessa_rerum_obscuritas_AdG|<span id="Accedit_confessa_rerum_obscuritas_AdG_back"><sup>'''2'''</sup></span>]]. Presque toujours l’action des astres se fait sentir sur la terre ou plus tôt ou plus tard qu’il ne devrait ; aussi dit-on communément, lorsque le beau temps est revenu, que tel astre a produit son effet. Ces phénomènes dépendent des astres fixés à la voûte des cieux, ainsi que des étoiles, dont les mouvemens particuliers excitent des grêles et des pluies qui sont d’une très-grande conséquence pour les biens de la terre, comme nous l’avons observé, et amènent dans la température des changemens sur lesquels le laboureur ne pouvait compter. Non-seulement les hommes y sont trompés, mais aussi les animaux, bien plus habiles que nous à prévoir ces vicissitudes, puisque d’ailleurs leur vie en dépend ; en effet, on a vu des oiseaux d’été périr par des froids arrivés trop tôt ou trop tard, et des oiseaux d’hiver par des chaleurs également imprévues. Aussi '''Virgile''' veut-il qu’on étudie aussi le cours des ''planètes'', et qu’on observe avec soin le passage du froid ''Saturne'' [[#Ideo_Virgilius_errantium_quoque_siderum_rationem_ediscendam_prœcipit_AdG|<span id="Ideo_Virgilius_errantium_quoque_siderum_rationem_ediscendam_prœcipit_AdG_back"><sup>'''3'''</sup></span>]].<br /><p style="text-align: justify; text-indent: 15px;">Quelques-uns fixent le commencement du printemps à l’apparition des papillons, parce que ces insectes sont fort délicats. Néanmoins on a observé, dans l’année même où j’écris cette partie de mon ouvrage, que le froid, ayant repris trois fois, a fait périr autant de fois les papillons, et que les hirondelles qui, s’étaient montrées dès le 6 des [[w:Calendes|''kalendes'']] de février, et semblaient annoncer le retour du printemps, ont eu à essuyer un rigoureux hiver.<br /><p style="text-align: justify; text-indent: 15px;">C’est donc une science très-problématique que celle de l’influence des astres, et les inductions qu’elle fournit sont fort douteuses [[#Res_anceps_AdG|<span id="Res_anceps_AdG_back"><sup>'''4'''</sup></span>]]. Ce qui augmente la difficulté, c’est la convexité du ciel et la différence des climats de la terre : le même astre se montre ici dans un temps, et là dans un autre ; d’où il suit que son influence ne se fait pas sentir en même temps partout. Pour surcroît d’embarras, les observations recueillies par les auteurs ont été faites dans des lieux différens, et ceux du même pays ne s’accordent pas même entre eux. On compte trois écoles astronomiques, la ''chaldéenne'', l’égyptienne et la ''grecque''. Le dictateur '''César''' en a fondé, chez les ''Romains'', une quatrième, lorsqu’aidé de '''Sosigène''', habile astronome, il fixa la longueur de l’année à une révolution du soleil. On trouva dans la suite que son calendrier était défectueux, parce que l’année, auparavant plus courte, se trouvait alors plus longue que le cours du soleil. Pour y remédier, il fallut, pour douze années consécutives, supprimer les jours intercalaires. '''Sosigène''' lui-même, le mathématicien le plus exact de son temps, après avoir revu jusqu’à trois fois ses calculs, sembla toujours douter de leur justesse, et ne cessa jamais de se corriger lui-même. De tous les auteurs qui ont traité ce sujet, et que nous avons cités au commencement de ce livre, il en est rarement deux qui soient de même avis. Cette divergence d’opinions est moins surprenante et plus excusable chez ceux qui écrivaient en des pays différens. Mais que dire de ceux qui, habitant le même pays, sont néanmoins d’avis différens ? En voici un exemple : '''Hésiode''', qui nous a laissé aussi un ouvrage sur le cours des astres, fixe le coucher matutinal des ''Pléiades'' au moment de l’équinoxe d’automne ; '''Thales''' prétend qu’il n’arrive que vingt-cinq jours après ; '''Anaximandre''' en met vingt-neuf ; '''Euctémon''', quarante-huit.<br /><p style="text-align: justify; text-indent: 15px;">Quant à nous, nous suivrons les calculs de '''César''', qui se rapportent spécialement à l’Italie ; mais nous rapporterons aussi les observations étrangères, car notre plan n’est pas de traiter d’un seul pays, mais de la nature entière. Pour être moins longs, nous citerons les pays, et non les auteurs ; et, pour abréger davantage encore, les lecteurs se souviendront que, sous le nom d’Attique, il faut aussi entendre les ''Cyclades'' ; sous le nom de ''Macédoine'', la ''Magnésie'' et la ''Thrace'' ; sous le nom d’Égypte , la ''Phénicie'', l’île de ''Cypre'' et la ''Cilicie'' ; sous celui de ''Béotie'', la ''Locride'', la ''Phocide'' et les contrées voisines ; sous le nom d’Hellespont, la ''Chersonèse'' et partie du continent jusqu’au mont ''Athos'' ; sous le nom de l’Ionie, l’Asie et les îles ''Asiatiques'' ; sous le nom du ''Péloponnèse'', l’Achaïe et les pays adjacens au couchant ; enfin sous le nom de ''Chaldée'', l’Assyrie et la ''Babylonie''. On ne sera pas étonné que nous ne parlions ni de l’Afrique, ni de l’Espagne, ni des ''Gaules''. Aucun auteur dans ces contrées n’a laissé d’observations sur le lever ou le coucher des astres. Il ne sera pas difficile néanmoins de déterminer l’époque de ces phénomènes dans ces contrées, en étudiant la disposition des cercles, telle que nous l’avons présentée dans le sixième livre. Par ce moyen, on déterminera la position astronomique, non-seulement de chaque pays, mais encore de chaque ville dont nous avons pu parler, en prenant par les ombres égales de tous les cercles, une portion du cercle de telle contrée qu’on voudra choisir, et en calculant son rapport avec le lever des astres. Il faut faire observer encore que tous les quatre ans les chaleurs reviennent à peu près les mêmes pour chaque saison, en raison du mouvement du soleil, et que toutes les huitièmes années elles sont plus fortes, à cause de la centième lunaison.</div> <table cellspacing=15 align=center style="margin: 0 4em; font-size:85%;"> <tr> <td style="text-align: justify; margin: 0 4em;">'''[[#Primum_omnium_dierum_AdG_back|<span id="Primum_omnium_dierum_AdG"><sup>1</sup></span>]] Cf. sur la plupart des difficultés que le texte de notre auteur révèle, les notes relatives à l’astronomie, au livre II. L’année romaine fut d’abord celle des [[w:Albe_la_Longue|''Albains'']], c’est-à-dire lunaire ; dix mois la composaient, mars en était le premier : elle avait cinquante jours de moins que l’année lunaire réelle, et soixante-un de moins que l’année solaire, c’est-à-dire trois cent quatre jours seulement ; c’était là l’[[w:Calendrier_romain|{{Info|''année de Romulus''|le calendrier dit romuléen}}]]. [[w:Calendrier_romain|{{Info|''Numa''|le calendrier dit pompilien}}]] ajouta deux mois à cette année, janvier et février, et elle se trouva être composée de trois cent cinquante-cinq jours. Elle demeura ainsi jusqu’à Jules César, où commence l’[[w:Calendrier_julien|''année julienne'']], qui se compose de trois cent soixante-cinq jours, huit heures, auxquels [[w:Calendrier_grégorien|''Grégoire le Grand'']] ajouta onze minutes, pour arriver à la plus grande exactitude possible.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Accedit_confessa_rerum_obscuritas_AdG_back|<span id="Accedit_confessa_rerum_obscuritas_AdG"><sup>2</sup></span>]] L’entrée du soleil dans tel ou tel signe du zodiaque, son passage à l’équateur, etc., ne sont pas toujours le signal d’un changement dans la température. [[w:Végèce|Végèce]] a parlé des jours prokéimasiques et épikéiniasiques :''' {{Info|''Aut enim circa diem statutum, aut ante, vel postea, tempestates fieri, compertum est : unde præcedentes, ωροϰεἰμασιν : nascentes die solenni, επιϰεἰμασιν : subsequentes, μεταϰεἰμασιν, græco vocabulo nuncuperaverunt''|Car on a constaté que les tempêtes se produisent soit vers le jour fixé, soit avant, soit après : d’où les précédents, ωροϰεἰμασιν : nés le jour solennel, επιϰεἰμασιν : les suivants, μεταϰεἰμασιν, étaient appelés par le mot grec. TdA.}} '''(IV, 40).''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Ideo_Virgilius_errantium_quoque_siderum_rationem_ediscendam_prœcipit_AdG_back|<span id="Ideo_Virgilius_errantium_quoque_siderum_rationem_ediscendam_prœcipit_AdG"><sup>3</sup></span>]] <p style="margin: 0 6em; text-indent: 0px">'''{{Info|Hoc metuens, cæli menses et sidera serva,<br />Frigida Saturni sese quo stella receptet.|Craignant cela, gardez les lunes et les étoiles du ciel, le froid de Saturne lui-même, où l’étoile les recevra. TdA.}}'''<p style="text-align: right; margin: 0 6em; text-indent: 0px">''Georg.'', I, 335.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Res_anceps_AdG_back|<span id="Res_anceps_AdG"><sup>4</sup></span>]] Voici enfin quelques idées philosophiques qui se trouvent sous la plume de Pline ; malheureusement la cause par laquelle notre auteur explique l’influence des astres est problématique, et montre que les sciences astronomiques des anciens laissaient beaucoup à désirer. Les Grecs croyaient qu’il y avait autant de cieux que de planètes ; le huitième ciel, ou le firmament, était celui dès étoiles fixés.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Occasum_matutinum_Vergiliarum_Hesiodus_AdG_back|<span id="Occasum_matutinum_Vergiliarum_Hesiodus_AdG"><sup>5</sup></span>]] L’ouvrage auquel Pline fait allusion a été mentionné par [[w:Théon_d'Alexandrie|Théon]] qui le nomme Αστριϰὴ βίϐλος [[w:en:Astronomia_(poem)|(en)]]. Cet ouvrage est perdu.'''</td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k5804072n <u>Histoire Naturelle de Pline. Tome Onzième</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k5804072n/f196.item ''Livre XVIII.''], [https://gallica.bnf.fr/ark:/12148/bpt6k5804072n/f340.item ''chap. LVII.''], traduction nouvelle par M. [[w:Stéphane_Ajasson_de_Grandsagne|Ajasson de Grandsagne]] [https://gallica.bnf.fr/ark:/12148/bpt6k5804072n/f453.item <sup>NOTES</sup>], C. L. F. Panckoucke, Paris, 1829</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: center; margin: 0 2em;">''Divi[[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki />ion des jours & des nuits [[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki />uivant le cours du Soleil ; lever & coucher des étoiles ; ordre des [[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki />ai[[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki />ons ; tems où l’on [[w:S_long|{{Info|ſ|forme ancienne longue de la lettre s minuscule}}]]<nowiki />eme les bleds d’hiver.''<br /><p style="text-align: justify; text-indent: 15px;">'''D'''’ABORD il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t pre{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que impo{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ible de déterminer au ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te le nombre des jours de l’année, & le cours du Soleil ; car comme aux trois cents {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oixante & cinq jours dont l’année e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t compo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ée, on ajoute le quart d’un jour & d’une nuit, autrement {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ix heures, qui, au bout de quatre ans, font un jour intercalaire [[#jour_intercalaire_LPdS|<span id="jour_intercalaire_LPdS_back"><sup>'''1'''</sup></span>]], il arrive qu’on ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}auroit a{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}igner avec certitude le tems du lever & du coucher des a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres. En {{Info|ſ|forme ancienne longue de la lettre s minuscule}}econd lieu, l’on convient qu’il y a dans cette théorie beaucoup d’ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}curité ; car quelquefois les {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ons [[#quelques_les_saisons_LPdS|<span id="quelques_les_saisons_LPdS_back"><sup>'''2'''</sup></span>]] commencent plu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ieurs jours avant le terme qui leur a été fixé, ce que les Grecs appellent ''prokheïma{{Info|ſ|forme ancienne longue de la lettre s minuscule}}is'' ; & d’autres fois plu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ieurs jours après, ce que ces mêmes Grecs expriment par le mot ''epikheïma{{Info|ſ|forme ancienne longue de la lettre s minuscule}}is'' : & l’on éprouve très {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ouvent que l’action des a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ait {{Info|ſ|forme ancienne longue de la lettre s minuscule}}entir {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur la terre, tantôt plutôt, tantôt plus tard. Au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i dit-on communément, lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que le beau tems e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t revenu, que tel a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tre a produit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on effet. D’ailleurs comme tout cela dépend des globes céle{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tes, leur mouvement relatif excite quelquefois des grêles & des pluies, qui, comme nous l’avons déja [[#déjà_fait_observer_LPdS|<span id="déjà_fait_observer_LPdS_back"><sup>'''3'''</sup></span>]] fait ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}erver, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont de la plus grande con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}équence pour les biens de la terre, & qui renver{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ent l’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pérance qu’on avoit du beau tems. Et non {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement les hommes y {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont trompés, mais au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i les animaux, quoiqu’ils aient bien plus de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}agacité que nous pour pre{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}entir ces vici{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}itudes du ciel, d’autant que leur vie en dépend. En effet, on voit quelquefois les oi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}eaux d’été mourir par des froids qui viennent trop tard ou trop tôt, & les oi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}eaux d’hiver par des chaleurs qui arrivent de même. C’est pourquoi '''Virgile''' veut qu’on étudie au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i le cours des planetes, & qu’on ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}erve à quelle partie du zodiaque répond la planete du froid Saturne [[#Saturne_LPdS|<span id="Saturne_LPdS_back"><sup>'''4'''</sup></span>]]. Quelques-uns croient que le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}igne le plus certain du printems commencé, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’on voit des papillons, & cela parceque ces in{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ectes {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont fort délicats. Néanmoins on a remarqué que dans l’année même [[#lAnnée_dÉcriture_LPdS|<span id="lAnnée_dÉcriture_LPdS_back"><sup>'''5'''</sup></span>]] où j’écris ceci, le froid ayant recommencé ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’à trois fois, a fait mourir autant de fois les papillons ; & que les hirondelles, qui, s’étant montrées dès le vingt-{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ept de Janvier, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}embloient annoncer le retour du printems, ont en{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uite e{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}uyé un très cruel hiver.<br /><p style="text-align: justify; text-indent: 15px;">C’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t donc une {{Info|ſ|forme ancienne longue de la lettre s minuscule}}cience très problématique que celle de l’influence des a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres, & les inductions que l’on en tire {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont fort douteuses. Mais ce qui cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e le plus d’incertitude, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t cette convexité du ciel & la différence des climats de la terre, parceque le même a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tre {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e montre ici dans un tems, & là dans un autre, d’où il ré{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ulte que {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on influence ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e fait pas {{Info|ſ|forme ancienne longue de la lettre s minuscule}}entir en même tems par-tout. Un autre {{Info|ſ|forme ancienne longue de la lettre s minuscule}}urcroît de difficulté, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t que les ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervations recueillies par les Auteurs ont été faites en différents lieux, & que ceux même qui ont écrit dans le même endroit ne s’accordent nullement entre eux dans ce qu’ils écrivent. On compte ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’à trois différentes {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ortes de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ectes en A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tronomie ; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}avoir, celle des Chaldéens [[#secte_des_Chaldéens_LPdS|<span id="secte_des_Chaldéens_LPdS_back"><sup>'''5*'''</sup></span>]], celle des Egyptiens & celle des Grecs. On peut même dire que le Dictateur Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar [[#Dictateur_César_LPdS|<span id="Dictateur_César_LPdS_back"><sup>'''6'''</sup></span>]] en produi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}it, chez les Romains, une quatrieme, lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’il rédui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}it chaque année au cours du {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oleil, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervant à cet effet du travail de So{{Info|ſ|forme ancienne longue de la lettre s minuscule}}igene, très habile A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tronome. Néanmoins on découvrit en{{Info|ſ|forme ancienne longue de la lettre s minuscule}}uite que le calendrier de Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar étoit défectueux [[#calendrier_César_défectueux_LPdS|<span id="calendrier_César_défectueux_LPdS_back"><sup>'''7'''</sup></span>]], parceque l’année qui auparavant étoit plus courte que le cours du {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oleil, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e trouvoit alors plus longue : & pour corriger cette erreur, on ordonna que pendant douze années de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uite, il n’y auroit point de jour intercalaire [[#correction_erreur_LPdS|<span id="correction_erreur_LPdS_back"><sup>'''8'''</sup></span>]]. So{{Info|ſ|forme ancienne longue de la lettre s minuscule}}igene lui-même, quoique Mathématicien plus exact que les autres, ne lai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}a pas de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e conduire con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tamment en homme qui doutoit de la ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}e de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on propre calcul, pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’il en fit ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’à trois différentes corrections. De tous les Auteurs [[#Auteurs_calendrier_LPdS|<span id="Auteurs_calendrier_LPdS_back"><sup>'''9'''</sup></span>]] qui ont écrit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur cette matiere, & que nous avons allégués au commencement de ce livre, il s’en trouve rarement deux qui {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oient de même {{Info|ſ|forme ancienne longue de la lettre s minuscule}}entiment. Cette variété e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t moins {{Info|ſ|forme ancienne longue de la lettre s minuscule}}urprenante, comme au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i plus excu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}able, chez ceux qui écrivoient en des pays différents. Mais que dire de ceux qui, écrivant dans le même pays, n’ont pas lai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}é d’être partagés d’opinion ? En voici un exemple. '''Hé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iode''', dont il y a au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i un ouvrage [[#ouvrage_astronomique_Hésiode_LPdS|<span id="ouvrage_astronomique_Hésiode_LPdS_back"><sup>'''10'''</sup></span>]] {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le cours des a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres, dit que les Pléiades {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e couchent le matin dans le tems même de l’équinoxe d’automne. '''Thalès''' [[#opinion_Thalès_Hésiode_Anaximandre_LPdS|<span id="opinion_Thalès_Hésiode_Anaximandre_LPdS_back"><sup>'''11'''</sup></span>]] dit que cela arrive vingt-cinq jours après. '''Anaximandre''' en met vingt-neuf ; '''Euctémon''' [[#Euctémon_LPdS|<span id="Euctémon_LPdS_back"><sup>'''12'''</sup></span>]] quarante-huit.<br /><p style="text-align: justify; text-indent: 15px;">Quant à nous, nous {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivrons le calcul de Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar, ayant principalement égard à l’Italie. Nous ne lai{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}erons néanmoins de rapporter les opinions étrangeres, parceque notre objet n’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t pas de traiter d’un {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eul pays, mais de la Nature entiere. Seulement, pour éviter les longueurs, nous n’indiquerons que les pays où chaque opinion a lieu, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ans faire mention des auteurs de ces a{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ertions : & pour abréger encore davantage, les Lecteurs voudront bien {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ouvenir que quand il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t parlé de l’Attique, il faut, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous ce nom, entendre au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i les Cyclades ; que {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le nom de Macédoine, il faut entendre au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i la Magné{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ie & la Thrace ; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le nom de l’Egypte, la Phénicie, l’i{{Info|ſ|forme ancienne longue de la lettre s minuscule}}le de Chypre & la Cilicie; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous celui de la Béotie, la Locride, la Phocide & les contrées voi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ines ; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le nom de l’Hel le{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pont, la pre{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’i{{Info|ſ|forme ancienne longue de la lettre s minuscule}}le de Thrace & le pays de terre ferme, ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’au mont Athos ; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le nom de l’Ionie, l’A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ie & les i{{Info|ſ|forme ancienne longue de la lettre s minuscule}}les A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iatiques ; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le nom du Péloponne{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e, l’Achaïe & les contrées [[#contrées_adjacentes_ouest_Péloponnese_LPdS|<span id="contrées_adjacentes_ouest_Péloponnese_LPdS_back"><sup>'''13'''</sup></span>]] adjacentes qu’elle a à {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on couchant ; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le nom des Chaldéens, l’Assyrie & la Babylonie. Il ne faudra pas s’étonner {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i je ne parle ici ni de l’Afrique, ni de l’Espagne, ni des Gaules, pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que ces pays n’ont eu aucun Auteur qui ait écrit du cours des a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres, ni de leur lever. Toutefois il ne {{Info|ſ|forme ancienne longue de la lettre s minuscule}}era pas difficile de connoître le tems où ils {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e levent dans ces pays-là même, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i l’on e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t in{{Info|ſ|forme ancienne longue de la lettre s minuscule}}truit de l’arrangement des cercles céle{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tes, tel que nous l’avons expliqué au {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ixieme livre de cet ouvrage ; car, par ce moyen, & par les {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eules notions que nous avons expo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ées en donnant une nomenclature des lieux, on {{Info|ſ|forme ancienne longue de la lettre s minuscule}}aura la po{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ition, non {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement de chaque pays, mais encore de chaque ville, en prenant par les ombres égales de tous les cercles, un {{Info|ſ|forme ancienne longue de la lettre s minuscule}}egment du cercle de tel pays qu’on voudra choi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ir, & en cherchant {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on rapport avec le lever des a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres. Il faut remarquer au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i que de quatre [[#Columelle_LPdS|<span id="Columelle_LPdS_back"><sup>'''14'''</sup></span>]] en quatre ans les {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ons & les chaleurs reviennent à peu près les mêmes, & cela à cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e du mouvement du Soleil ; & que de huit en huit ans ces mêmes chaleurs reviennent plus con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}idérables, en vertu de la centieme lunai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}on.</div> {{Boîte déroulante début|titre=Notes du traducteur|alignT=center}} <div style="text-align: justify; border: 2px; border-radius:15px; font-size:85%;"><br/> <table cellspacing=15 align=center style="margin: 0 4em;"> <tr> <td style="text-align: justify; margin: 0 4em; text-indent: 15px">'''[[#jour_intercalaire_LPdS_back|<span id="jour_intercalaire_LPdS"><sup>1</sup></span>]] Con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ultez, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur toute cette que{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tion, nos notes 13 & 14 {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le huitieme chapitre du {{Info|ſ|forme ancienne longue de la lettre s minuscule}}econd livre de Pline, tome 1, p. 41 & 42 :<br /><p style="text-align: justify; text-indent: 15px; margin: 0 5em;">(13) [[w:Suétone|Suétone]] s’exprime mieux, lor{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’il écrit : chaque quatrieme année ''quarto quoque anno''. Il est vrai que, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}elon le [[w:Jean_Hardouin|Pere Hardouin]], il faut comprendre dans la période de cinq ans, dont parle Pline, la premiere & la cinquieme année comme Bi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}extiles ; ce qui revient aux quatre années de Suétone, dont la quatrieme avoit un ''bi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ex'' ; mais en vérité cette explication est des plus forcées. Je {{Info|ſ|forme ancienne longue de la lettre s minuscule}}erois donc d’avis qu’il faudroit lire dans Pline comme dans Suétone, ''quarto anno'', {{Info|ſ|forme ancienne longue de la lettre s minuscule}}i nous n’apprenions d’ailleurs de cet Hi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}torien, que dès le regne d’Augu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te il s’étoit déja gli{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}é plu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ieurs abus & altérations dans l’année Julienne. On voit du moins qu’il y réforma plu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ieurs cho{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es, sous prétexte de la remettre {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le pied où Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar l’avoit in{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tituée. Cela me donneroit à pen{{Info|ſ|forme ancienne longue de la lettre s minuscule}}er qu’on fit dès-lors attention à la fau{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}eté & à l’excédence du calcul Julien ; mais qu’Augu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te, par respect pour la mémoire de Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar, affecta d’imputer la faute à la négligence des Prêtres chargés à Rome de l’in{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pection du Calendrier ; qu’au demeurant, on découvrit l’abus, & qu’on e{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}aya d’y remédier, en n’ajoûtant un jour entier à l’année ordinaire que chaque ''cinquieme année'' comme Pline paroît l’articuler ici expre{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ement. Mais d’ailleurs il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t évident qu’à la longue le période bi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}extile de quatre années en quatre années prévalut {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur celui de chaque cinquieme année, dont parle Pline ; & même il paroît que ceux qui, par la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uite, voulurent {{Info|ſ|forme ancienne longue de la lettre s minuscule}}upputer les tems, récapitulerent, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ans exception, toutes les bi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}extiles quartenaires écoulées depuis l’in{{Info|ſ|forme ancienne longue de la lettre s minuscule}}titution Julienne ; car en 1582, on trouva par ce moyen que l’année étoit reculée de dix jours & plus; d’autant que l’excédence du calcul Julien, qui {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uppo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e à l’année révolue 365 jours & {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ix heures, au lieu de 365 jours 5 heures 49 minutes, 8 {{Info|ſ|forme ancienne longue de la lettre s minuscule}}econdes 17 tierces & 13 quarts qu’elle a réellement, forme tous les ans environ 11 minutes de trop, & tous les cent trente-quatre ans un jour entier d’excès. Le Pape Grégoire XIII trouvant donc l’année reculée de plus de dix jours ; ce qui dérangeoit l’économie annuelle des {{Info|ſ|forme ancienne longue de la lettre s minuscule}}olemnités, remédia à cet inconvénient en retranchant de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on autorité dix jours au mois d’Octobre de l’année 1582, où l’on étoit alors ; & en réglant qu’à l’avenir tous les quatre cents ans on omettroit trois années bi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}extiles. Ce réglement devint une loi pour pre{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que toute l’Europe chrétienne. L’autre maniere de compter fut appellée l’ancien {{Info|ſ|forme ancienne longue de la lettre s minuscule}}tyle. La Grande-Bretagne a long-tems per{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i{{Info|ſ|forme ancienne longue de la lettre s minuscule}}té à s’en {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervir malgré {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on abus manife{{Info|ſ|forme ancienne longue de la lettre s minuscule}}te. Enfin le Parlement d’Angleterre, par acte du mois de Septembre 1752, a adopté la réforme Grégorienne.<br /><p style="text-align: justify; text-indent: 15px; margin: 0 4em;">(14) Le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oleil, en fai{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ant le tour du cercle oblique, parcourt réellement 360 degrés ou {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ections ; pui{{Info|ſ|forme ancienne longue de la lettre s minuscule}}que tout cercle {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e divi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e en 360 parties appellées degrés : mais la me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ure de chaque degré du cercle parcouru annuellement par le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oleil, excede tant {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oit peu, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t-à-dire de quelques légeres fractions de tems, la durée de chacun de nos jours révolus ; durée qui n’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t, comme on {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ait, que de 24 heures préci{{Info|ſ|forme ancienne longue de la lettre s minuscule}}es ; le{{Info|ſ|forme ancienne longue de la lettre s minuscule}}quelles, comparées à un degré, en produi{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ent, au bout de l’année, 365 & plus pour le cercle, au lieu de 360 {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eulement que le cercle requerroit. D’après une connoi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ance con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}u{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e de ces principes, Jules Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e voyant Dictateur, Grand-Pontife, & maître du monde, entreprit, l’an 140 avant J. C. de réformer les abus qui s’étoient gli{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}és, tant dans l’année Pompilienne, ou de Numa, que dans celle des Pontifes, encore plus irréguliere que celle de Numa. A cet effet, il fit venir d’Alexandrie le Philo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ophe So{{Info|ſ|forme ancienne longue de la lettre s minuscule}}igenes. Celui-ci décida fau{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ement que le cercle des jours de l’année révolue excédoit du nombres 5 joint au quart de 1 les 360 degrés du cercle oblique parcouru par le {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oleil : expo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}é faux, auquel le Dictateur, occupé d’autres {{Info|ſ|forme ancienne longue de la lettre s minuscule}}oins, déféra {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ans autre examen. Jules Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar régla donc, de l’avis de {{Info|ſ|forme ancienne longue de la lettre s minuscule}}on A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tronome, que l’année {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit divisée en 365 jours ; & quant au quart de jour re{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tant, qui produit {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ix heures, il ordonna qu’on n’y auroit aucun égard pour chaque année particuliere ; mais que chaque quatrieme année on réuniroit la totalité de quatre fois {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ix heures, qui en font vingt-quatre, pour en compo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}er un jour entier; & qu’ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i cette quatrieme année auroit 366 jours. Il régla de plus, que ce jour intercalaire, ou ajoûté à chaque quatrieme année, seroit le 24 Février. Les Romains nommoient ce jour-là ''bis {{Info|ſ|forme ancienne longue de la lettre s minuscule}}exto calendas Martii'', c’est-à-dire, le ''{{Info|ſ|forme ancienne longue de la lettre s minuscule}}econd {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ixieme avant les calendes de Mars'' ; d’où il arriva que l’année où tomboit ce jour intercalaire fut appellée bis-{{Info|ſ|forme ancienne longue de la lettre s minuscule}}extile.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#quelques_les_saisons_LPdS_back|<span id="quelques_les_saisons_LPdS"><sup>2</sup></span>]] Végece dit pareillement, liv. 4, chap. 40 : ''Aut enim circa diem {{Info|ſ|forme ancienne longue de la lettre s minuscule}}tatutum, aut ante, vel po{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tea, tempe{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tates fieri compertum e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t : unde præcedentes, ωροϰεἰμασιν : na{{Info|ſ|forme ancienne longue de la lettre s minuscule}}centes die {{Info|ſ|forme ancienne longue de la lettre s minuscule}}olenni, επιϰεἰμασιν : {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ub{{Info|ſ|forme ancienne longue de la lettre s minuscule}}equentes, μεταϰεἰμασιν, Græco vocabulo nuncuperaverunt'' [[#Accedit_confessa_rerum_obscuritas_AdG_back|<sup>⤴️</sup>]]. On lit au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i chez [[w:Columelle|Columelle]], dans {{Info|ſ|forme ancienne longue de la lettre s minuscule}}a Préface :''' {{Info|''Neque enim ſemper eumdem, cælum & annus, velut ex præſcripto habitum gerunt : nec omnibus annis eodem vultu venit aſtas, aut hyems, &c.''|Car le ciel et l’année ne portent pas toujours le même habit, comme par un précepte : ni l’automne n’arrive chaque année avec la même apparence, ni l’hiver, etc. TdA}} </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#déjà_fait_observer_LPdS_back|<span id="déjà_fait_observer_LPdS"><sup>3</sup></span>]] Au liv. 17, chap. 2.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Saturne_LPdS_back|<span id="Saturne_LPdS"><sup>4</sup></span>]] Ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i qu’il l’in{{Info|ſ|forme ancienne longue de la lettre s minuscule}}inue dans {{Info|ſ|forme ancienne longue de la lettre s minuscule}}es ''Géorg.'' liv. I, v. 335 :'''<br /><p style="margin: 0 6em; text-indent: 0px">{{Info|Hoc metuens, cæli menses et sidera serva,<br />Frigida Saturni sese quo stella receptet.|Craignant cela, gardez les lunes et les étoiles du ciel, le froid de Saturne lui-même, où l’étoile les recevra. TdA.}} </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#lAnnée_dÉcriture_LPdS_back|<span id="lAnnée_dÉcriture_LPdS"><sup>5</sup></span>]] Pline, au quatorzieme livre, chap. 4, comptoit deux cents trente ans depuis la mort de [[w:Cicéron|Cicéron]] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Cicéron_I|<sup>🔄</sup>]], arrivée l’an de Rome 600. L’année qu’il indique ici, & où il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e trouvoit avoir compo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}é quatre livres de plus, e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t probablement la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivante, c’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t-à-dire l’année 831 de la fondation de Rome.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#secte_des_Chaldéens_LPdS_back|<span id="secte_des_Chaldéens_LPdS"><sup>5*</sup></span>]] Sur l’année Chaldéenne, qui étoit la même que la Judaïque, con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ultez [[w:Eusèbe_de_Césarée|Eu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ebe]], ''Præpar. Evang.'' liv. 9, chap. 17, où il fait Abraham inventeur de l’a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tronomie chez les Chaldéens. Les A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}trologues Chaldéens étoient ordinairement des Prêtres des Dieux, tels que [[w:Bérose|Béro{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e]], auquel les Athéniens éleverent dans leur Gymna{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e une {{Info|ſ|forme ancienne longue de la lettre s minuscule}}tatue à langue dorée. Sur quoi voyez Pline, liv. 7, chap. 37. Ce Béro{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e étoit un Prêtre de [[w:Bēl|Belus]] ; il e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}t cité par [[w:Clément_d'Alexandrie|Clément d’Alexandrie]], & par [[w:Flavius_Josèphe|Jo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}eph]] [[#Flavius_Josèphe_I|<sup>⤵️</sup>]], contre Apion, liv. 1. Sur l’année Egyptienne, & {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur l’ancienne année Grecque, con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ultez [[w:Hérodote|Hérodote]] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Hérodote_I|<sup>🔄</sup>]] liv. 2, n°. 4. Cicéron rend ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tice à l’étude que firent des a{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tres les Egyptiens & les Babyloniens, liv. 1, de ''Divinat.'' n°. 16 :''' {{Info|''Ægyptii, & Babylonii, in camporum patentium aquoribus habitantes, cùm ex terra nihil emineret, quod contemplationi cæli officere poſſet, omnem curam in ſiderum cognitione poſuerunt''|Les Égyptiens et les Babyloniens, vivant dans les eaux des plaines découvertes, alors que rien ne dépassait de la terre qui pût gêner la contemplation du ciel, mettaient tous leurs soins dans la connaissance des étoiles. TdA}}. </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Dictateur_César_LPdS_back|<span id="Dictateur_César_LPdS"><sup>6</sup></span>]] Voyez les notes 13 & 14 {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur le chap. 8 du liv. 2, tome 1, p. 41 & 42.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#calendrier_César_défectueux_LPdS_back|<span id="calendrier_César_défectueux_LPdS"><sup>7</sup></span>]] Voyez les notes indiquées dans la note précédente ; & joignez-y les ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervations {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivantes, qui {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont de M. De{{Info|ſ|forme ancienne longue de la lettre s minuscule}}places, p. 339 : « Le calendrier chrétien, ayant {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivi la réformation de Jules Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar, il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e trouva qu’en l’année 1582, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ous le Pape Grégoire XIII, l’équinoxe étoit remontée ju{{Info|ſ|forme ancienne longue de la lettre s minuscule}}qu’au 11 de Mars, au lieu du 21, où elle devoit être. Ce Pape, après avoir con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ulté Clavius & Ciaconius, les plus habiles A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tronomes du tems, ordonna qu’en cette même année 1582, on compteroit le 5 du mois d’Octobre, au lieu du 15, afin de retrancher les dix jours qui s’étoient gli{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}és de trop, en {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uivant la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}upputation Julienne, depuis le Concile de Nicée, tenu en 325 : on convint encore de continuer l’intercalation d’un jour tous les quatre ans ; & qu’en outre, pour éviter dans la {{Info|ſ|forme ancienne longue de la lettre s minuscule}}uite pareille erreur, il {{Info|ſ|forme ancienne longue de la lettre s minuscule}}eroit fait un retranchement de trois jours intercalaires, dans l’e{{Info|ſ|forme ancienne longue de la lettre s minuscule}}pace de quatre {{Info|ſ|forme ancienne longue de la lettre s minuscule}}iecles, à cau{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e des onze minutes qui manquent aux {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ix heures des années, dont on compo{{Info|ſ|forme ancienne longue de la lettre s minuscule}}e l’année intercalaire, ou bi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}extile ; ces trois jours {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e retranchent en l’année qui finit les trois premiers {{Info|ſ|forme ancienne longue de la lettre s minuscule}}iecles. De célebres A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}tronomes modernes ont fait voir que, malgré cette précaution, il y auroit encore, au bout de quatre cents ans, plu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ieurs jours de variation dans l’équinoxe ».''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#correction_erreur_LPdS_back|<span id="correction_erreur_LPdS"><sup>8</sup></span>]] Etabli par Jules Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar, & qui revenoit tous les quatre ans. Ecoutons Suétone, vie de ce Dictateur, chap. 40 : {{Info|''Faſtos correxit, jampridem vitio Pontiſicum, per intercalandi licentiam adeo turbatos, ut neque meſſium feria aſtati, neque vindemiarum autumno competerent, annumque ad curſum ſolis accommodevit, ut CCCLXV dierum eſſet, & intercalario menſe ſublato, unus dies quarto quoque anno intercalaretur, &c''|Il corrigea les jeûnes, qui avaient toujours été une faute pontique, si perturbés par la permission de l’intercalation, que ni les fêtes du mois ne tenaient, ni les récoltes ne correspondaient à l’automne, et il ajusta l’année à la course du soleil, de sorte que c’était 365 jours, et après le mois intercalaire, un jour était intercalé tous les quatre ans, etc. TdA}}. On s’apperçut que cette correction de Jules Cé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ar étoit elle-même fautive. On tenta de nouveau de remédier au vice du calendrier ; {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur quoi con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ultez Solin, chapitre 1, p. 5 ; le P. Petau, ''de Doctr. temp.'' chap. 3 ; mais {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur-tout [[w:Macrobe|Macrobe]], qui s’exprime ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i, liv. 1, ''Saturn.'' chap. 14, p. 255 :''' {{Info|''Sacerdotes ſibi errorem novum ex ipſa emendatione ſecerunt. Nam cùm oporteret diem, qui ex quadrantibus conſit, quarto quoque anno conſecto, antequam quintus inciperet, intercalare, illi quarto non peracto ſed incipiente, intercalabant. Hic error ſex & triginta annis permanſit : quibus annis intercalati ſunt dies duodecim, cùm deberent intercalari novem. Sed hunc quoque errorem ſerò deprehenſum correxit Auguſtus, qui annos duodecim ſine intercalari die tranſigi juſſit : ut illi tres dies, qui per annos triginta & ſex vitio ſacerdotalis ſeſtinationis excreverant, ſequentibus annis duodecim, nullo die intercalato, devorarentur. Poſt hoc unum diem, ſecundùm ordinationem Caſaris, quinto quoque incipiente anno intercalari juſſit : & omnem hunc ordinem area tabula ad aternam cuſtodiam inciſione mandavit.''|Les prêtres retranchèrent une nouvelle erreur de leur correction. Car lorsqu’il fallait intercaler le jour qui est composé de quadrants, la quatrième année consécutive, avant que la cinquième ne commence, on intercalait ceux lorsque la quatrième n’était pas terminée mais commençait. Cette erreur a duré trente-six ans : années au cours desquelles douze jours ont été intercalés, alors qu’il aurait fallu en intercaler neuf. Mais cette erreur fut également détectée par Auguste, qui ordonna que douze années s’écoulèrent sans jour intercalaire : afin que ces trois jours, qui avaient été excrétés pendant les trente-six années du vice sacerdotal de cessation, soient dévorés dans le douze années suivantes, sans jour intercalaire. Après ce jour, selon l’ordonnance de César, il décréta que la cinquième année serait également intercalée : et tout l’ordre fut ordonné d’être gravé par le conseil du domaine pour la garde éternelle. TdA}}. </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Auteurs_calendrier_LPdS_back|<span id="Auteurs_calendrier_LPdS"><sup>9</sup></span>]] Ces Auteurs {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ont Hiéron, Philomêtor, Attale, Archelaüs, Xénophon, Magon, Caton, Silanus, Varron, &c. dont Pline a fait mention {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur la fin du chapitre 3.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#ouvrage_astronomique_Hésiode_LPdS_back|<span id="ouvrage_astronomique_Hésiode_LPdS"><sup>10</sup></span>]] Nous apprenons de Théon que cet ouvrage {{Info|ſ|forme ancienne longue de la lettre s minuscule}}e nommoit l’A{{Info|ſ|forme ancienne longue de la lettre s minuscule}}trique, Ἀςριϰὴ ϐίϐλος. Voyez au{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}i {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur ce même ouvrage, une Epigramme de [[w:Callimaque_de_Cyr%C3%A8ne|Callimaque]] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Callimaque_I_de_Cyrène_II|<sup>🔄</sup>]], citée dans la vie d’Aratus, qui fait partie de l’Uranologie de [[w:Denis_Pétau|Petau]], liv. 2, ''Var. Di{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ert.'' chap. 9, p. 97.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#opinion_Thalès_Hésiode_Anaximandre_LPdS_back|<span id="opinion_Thalès_Hésiode_Anaximandre_LPdS"><sup>11</sup></span>]] Con{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ultez, {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur cette opinion de Thalès, ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i que {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur celles d’Hé{{Info|ſ|forme ancienne longue de la lettre s minuscule}}iode & d’Anaximandre, l’Uranologie citée note précédente.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Euctémon_LPdS_back|<span id="Euctémon_LPdS"><sup>12</sup></span>]] ''Euctemon'' ; ain{{Info|ſ|forme ancienne longue de la lettre s minuscule}}i portent les manu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}crits, & non pas ''Eudemon''. J’ai traité d’Euctêmon (en Grec Εὐϰτημον) dans les notes alphabétiques du premier livre, & plus récemment dans la note 21 du chapitre précédent.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#contrées_adjacentes_ouest_Péloponnese_LPdS_back|<span id="contrées_adjacentes_ouest_Péloponnese_LPdS"><sup>13</sup></span>]] Telles que l’Elide, l’Arcadie, la Me{{Info|ſ|forme ancienne longue de la lettre s minuscule}}lénie.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Columelle_LPdS_back|<span id="Columelle_LPdS"><sup>14</sup></span>]] Columelle, liv. 3, chap. 6 :''' {{Info|''Quo tempore ſol in eamdem partem ſigniſeri per eoſdem numeros redit, per quos cursus ſui principium cæperat : quem circuitum meatus dierum integrorum mille quadringentorum ſexaginta unius [[w:Apocatastase|ὰ τοκατάςασιν]] vocant ſtudioſi rerum cæleſtium''|A ce moment-là, le soleil revient dans la même direction au moyen des mêmes nombres par lesquels il a commencé sa course : laquelle course de mille quatre cent soixante et un jours entiers est appelée ὰ τοκατάςασιν par ceux qui étudient les choses célestes. TdA}}.</td> </tr> </table><br/><br/></div> {{Boîte déroulante fin}} <div style="text-align: right; margin: 0 2em 0 1em;">[https://books.google.fr/books?id=JeyFTzG771cC&newbks=1&newbks_redir=0&dq=Louis%20Poinsinet%20de%20Sivry%20Pline%20l'ancien&hl=fr&pg=PP9#v=onepage&q&f=true <u>Histoire Naturelle de Pline. Tome Sixieme</u>], [https://books.google.fr/books?id=JeyFTzG771cC&newbks=1&newbks_redir=0&dq=Louis%20Poinsinet%20de%20Sivry%20Pline%20l'ancien&hl=fr&pg=PA257#v=onepage&q&f=true ''Livre Dix-huitieme.''], [https://books.google.fr/books?id=JeyFTzG771cC&newbks=1&newbks_redir=0&dq=Louis%20Poinsinet%20de%20Sivry%20Pline%20l'ancien&hl=fr&pg=PA455#v=onepage&q&f=true ''Diviſion des jours & des nuits ſuivant le cours du Soleil ; lever & coucher des étoiles ; ordre des ſaiſons ; tems où l’on ſeme les bleds d’hiver.''], traduction en françois, avec le texte latin rétabli d’après les meilleures leçons manu{{Info|ſ|forme ancienne longue de la lettre s minuscule}}crites ; accompagnée de Notes critiques pour l’éclairci{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ement du texte, & d’Ob{{Info|ſ|forme ancienne longue de la lettre s minuscule}}ervations {{Info|ſ|forme ancienne longue de la lettre s minuscule}}ur les connoi{{Info|ſſ|forme ancienne longue de la lettre s minuscule}}ances des Anciens comparées avec les découvertes des Modernes, par M. [[w:Louis_Poinsinet_de_Sivry|Louis Poinsinet de Sivry]], Chez la veuve Desaint, Paris, 1771</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Flavius_Josèphe|'''Flavius Josèphe''']] [[#Flavius_Josèphe|<span id="Flavius_Josèphe_back"><sup>'''I'''</sup></span>]] == <p style="text-align: right;">([[w:37|37]]/[[w:38|38]], à [[w:Histoire_de_Jérusalem#Période_romaine_et_byzantine_(63_av._J.-C._-_638)|''Jérusalem'']] — vers [[w:100|100]], à [[w:Rome_antique|''Rome'']]) [[w:Ier_siècle|<sup>⏳</sup>]] [[s:Auteur:Flavius_Josèphe|<sup>📚</sup>]] <div style="text-align: justify; margin: 0 1em; text-indent: 15px">[[w:historiographe|Historiographe]] ''romain'' [[w:Juifs|''juif'']] d’origine [[w:Judée_(province romaine)|''judéenne'']], il participe activement au début de la ''première guerre judéo-romaine'' en tant que commandant militaire de ''Galilée'' contre les Romains, avant de se rendre à [[w:Vespasien|'''Vespasien''']] [[#Vespasien|<span id="Vespasien_back"><sup>'''II'''</sup></span>]] lors de la prise de la garnison juive de la forteresse de [[w:Jotapata|''Jotapata'']] en juillet 67, et de devenir intermédiaire, interprète et négociateur entre les ''romains'' et les ''Juifs'' lors du siège de ''Jérusalem'' conduit par '''Titus''' [[#Titus_back|<sup>⤴️</sup>]] en 70. Après la fin de la grande révolte ''judéenne'', en 71, il s’établit auprès de son protecteur à ''Rome'' où il obtient la [[w:citoyenneté_romaine|''citoyenneté romaine'']].</div> {{Boîte déroulante début|titre=NdA Flavius Josèphe|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Flavius_Josèphe_back|<span id="Flavius_Josèphe"><sup>I</sup></span>]] De son nom de naissance Joseph ben (fils de) Matthatias, de l’hébreu יוסף בן מתתיהו / Yossef [[wikt:en:יוסף#Hebrew|(en)]] ben [[wikt:en:בן#Noun|(en)]] Matityahou [[wikt:en:מתתיהו#Hebrew|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">• de la troisième personne du singulier [[w:Jussif|''jussive'']], signifiant ainsi « peut-il ajouter », du verbe הוֹסִיף / hosíf [[wikt:en:הוסיף#Hebrew|(en)]], « 1. Ajouter (quelque chose) à (quelque chose d'autre). 2. (littéraire) Continuer (à faire quelque chose). 3. (archaïque) Coordonné avec un autre verbe pour indiquer que l’action de ce verbe "ajoute" d’une manière ou d’une autre. »;<br /><p style="margin: 0 2em; text-indent: 15px;">• du nom commun בֵּן / bén, « 1. Fils. 2. (ne produit plus de mot ou d’expression) Un descendant mâle direct. 3. Un mec, un garçon. 4. (n’est plus productif, état de construction) Possesseur de (connaissance, capacité, etc.). 5. (État construit) Utilisé pour exprimer l’âge d’un homme, d’un garçon ou le référent d’un nom masculin : âge, âgé. »;<br /><p style="margin: 0 2em; text-indent: 15px;">• de la contraction du nom commun מַתָּנַת / mataná [[wikt:en:מתנה#Hebrew|(en)]], « cadeau, présent, don »;<br /><p style="margin: 0 2em; text-indent: 30px;">➥ du verbe נתן / natán [[wikt:en:נתן#Verb|(en)]], « 1. Donner. 2. Autoriser, permettre, laisser. 3. (archaïque) Mettre, placer. 4. (archaïque, hébreu biblique) Se transformer en. »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ et du nom propre יהוה / YHWH [[wikt:en:יהוה#Hebrew|(en)]], « (Judaïsme) Tétragramme : mot en quatre lettres hébraïques utilisé comme nom [[wikt:ineffable#Français|''ineffable'']] de Dieu dans la Bible hébraïque, diversement rendu par Yahweh ou Jéhovah. »;<br /><p style="margin: 0 2em; text-indent: 15px;">Son tria nomina à l’obtention de sa ''citoyenneté romaine'' est {{Info|Titus|praenomen, nom individuel du citoyen romain}} {{Info|Flavius|nomen, nom de famille}} {{Info|Josephus|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}}, prenant ainsi le nom de son bienfaiteur :<br /><br /><p style="margin: 0 2em; text-indent: 15px;">[[#Vespasien_back|<span id="Vespasien"><sup>II</sup></span>]] [[w:Nom_romain|Tria Nomina]] en latin {{Info|Titus|praenomen, nom individuel du citoyen romain}} {{Info|Flavius|nomen, nom de famille}} {{Info|Vespasianus|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}};<br /><p style="margin: 0 2em; text-indent: 15px;">[[w:L%C3%A9gat_(Rome_antique)|''Légat'']] lors de la [[w:Conqu%C3%AAte_romaine_de_la_Grande-Bretagne|''conquête de la Bretagne en 43'']] et lors de la [[w:Premi%C3%A8re_guerre_jud%C3%A9o-romaine|''rébellion juive de 66'']] en [[w:Jud%C3%A9e_(province_romaine)|''Judée'']]. Fin décembre 69, il est couronné empereur par le [[w:S%C3%A9nat_romain|''Sénat'']] après la guerre civile de l’[[w:Ann%C3%A9e_des_quatre_empereurs|''année des quatre empereurs'']], et fonde la dynastie des [[w:Flaviens|''Flaviens'']].<br /><p style="text-align: right; margin: 0 2em;">(17 novembre [[w:9|9]], près de [[w:Reate|''Reate'']] dans la région centrale italienne du [[w:Latium|''Latium'']] — 23/24 juin [[w:79|79]], à la station thermale de [[w:Aquae_Cutiliae|''Aquae Cutiliae'']], à l’est de ''Reate'')<sup>[[w:Ier_siècle|⏳]]</sup><br/><br/></div> ''' {{Boîte déroulante fin}} === [[w:Contre_Apion|Contre Apion]] === <p style="text-align: right;">[[s:Contre_Apion|📚]] {| cellpadding="0" align="{{{align|right}}}" style="margin-left: 2em; width:40%; border-spacing:3px; text-align:center; background-color:#F8F9FA; border:2px solid #C8CCD1" |- | style="border:solid 1px #F8F9FA" | [https://archive.org/details/contreapiontexte0000jose/page/n156/mode/1up {{Info|'''Éditions & Manuscrits'''|Flavius Josèphe Contre Apion, Théodore Reinach & Léon Blum, 1930}}] |- style="font-size:8pt; line-height:10pt; vertical-align:middle" | align="center" | <div style="margin-right: 1em; padding:0 0 4px 0; text-align:justify;">{{{légende| * La traduction ''latine'' a été imprimée dès 1480 à [[w:Vérone#Époque_moderne|''Vérone'']] par '''Pierre Maufer''' [[w:en:Petrus_Maufer|(en)]]. L’édition de cette traduction, due à [[w:Sigismund_Gelenius|'''Sigismond Gelenius''']] (''Paris'', 1535), qui constitue la « [[w:Vulgate|Vulgate]] », présente un texte souvent « amélioré » de façon arbitraire. La seule édition critique est celle de '''Ch. Boysen''' (''Vienne'', 1898) qui fait partie du [[w:Corpus_scriptorum_ecclesiasticorum_latinorum|{{Info|''Corpus scriptorum ecclesiasticorum latinorum''|Le corpus des écrits ecclésiastiques latins}}]]. Elle repose principalement sur les manuscrits L(aurentianus LXVI, 2), B(odleianus Canonicianus 148), R(egius Parisinus 5049) de la I<sup>re</sup> classe, C(heltenhamensis Phillipicus 12311), P(arisinus 1615), Pa(rsinus 5054) de la seconde. * Le texte grec est édité pour la première fois dans l’édition complète de '''Josèphe''' à [[w:Bâle#XVIe_siècle|''Bâle'']], chez [[w:Johann_Froben|''Froben'']], en 1544 par [[w:Arnoldus_Arlenius|'''Arlenius''']] qui a peut-être fait usage du ''Schleusingensis'' (un des fils du Laurentianus), mais a introduit grand nombre de corrections tantôt heureuses, et tantôt arbitraires. Par la suite notre traité n’a guère été imprimé que comme partie intégrante d’éditions complètes des œuvres de '''Josèphe'''. Les plus importantes sont celles de '''E. Bernard''' (1700), de [[w:John_Hudson_(classiciste)|'''Hudson''']] (1720) — le premier qui ait utilisé L —, de [[w:Sigebert_Havercamp|'''Havercamp''']] (1726), simple compilateur, de [[w:Karl_Wilhelm_Dindorf|'''L. Dindorf''']] (1847), de [[w:Immanuel_Bekker|'''Imm. Bekker''']] (1856), tout à fait manquée. L’édition critique de '''Niese''' [[w:en:Benedikt_Niese|(en)]] (1889) est la base de tous les travaux ultérieurs, notamment des éditions de '''Naber''' (1896) et de [[w:Henry_St._John_Thackeray|'''H. St. J. Thackeray''']] (coll. Loeb, 1926 : il n’a encore paru que le ''C. Apion'', la ''Vita'' et le commencement de la ''Guerre'') qui ont pu profiter aussi des conjectures de '''Cobet''', de '''Holwerda''', et de '''Herwerden'''.}}}</div> |} <div style="text-align: justify; margin: 0 1em; text-indent: 15px">Dernière œuvre écrite par '''Flavius Josèphe''', vers [[w:93|93]], dont l’objectif est de répondre aux critiques qu’ont soulevées ses [[w:Antiquit%C3%A9s_juda%C3%AFques|''Antiquités judaïques'']], de défendre l’ancienneté du peuple ''juif'' et du [[w:Judaïsme#Judaïsme_antique|''judaïsme'']] (Livre I) et les accusations d’[[w:Apion_(grammairien)|'''Apion''']] [[#Apion|<span id="Apion_back"><sup>'''I'''</sup></span>]] (Livre II).</div> {{Boîte déroulante début|titre=NdA Apion|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Apion_back|<span id="Apion"><sup>I</sup></span>]] Du nom propre grec ancien [[wikt:Apion#Latin|Ἀπίων / Apíôn]]; <br/><br /><p style="margin: 0 2em; text-indent: 15px;">'''[[w:Grammaticus|''Grammairien'']] [[#grammairien|<span id="grammairien_back"><sup>II</sup></span>]] et [[w:Polygraphe_(auteur)|''polygraphe'']] [[#polygraphe|<span id="Grammairien_back"><sup>III</sup></span>]] ''grec'' d’Alexandrie.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] seconde moitié du [[w:Ier_siècle_av._J.-C.|I<sup>er</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]] — première moitié du [[w:Ier_siècle|I<sup>er</sup> siècle {{Info|EC|de l’Ère Commune}}]]) <br /><br /><p style="margin: 0 2em; text-indent: 15px;">'''[[#grammairien_back|<span id="grammairien"><sup>II</sup></span>]] Du nom commun latin grammaticus [[wikt:en:grammaticus#Latin|(en)]]; du nom commun grec ancien γραμματικός / grammatikós [[wikt:en:γραμματικός#Noun|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">➥ du nom commun γρᾰ́μμᾰ / grámma [[wikt:en:γράμμα#Noun|(en)]], « 1. Ce qui est écrit, ce qui est dessiné. 2. Lettre. 3. (au pluriel) Alphabet. 4. Écriture, livre. »;<br /><p style="margin: 0 2em; text-indent: 30px">➥ du verbe γράφω / gráphō [[wikt:en:γράφω#Ancient_Greek|(en)]], « 1. (Homérique) égratigner, couper en dedans. 2. Dessiner, esquisser, peindre. 3. Écrire. 4. Écrire, proposer une loi. 5. ([[w:Diathèse#Moyen|''voix moyenne'']]) : • (''[[w:Réflexivité_(grammaire)|réflexif]] indirect'') Écrire pour soi, noter ; • Accuser, poursuivre. 6. ([[w:Conjugaison_latine|''passif parfait'']]) Être écrit, être sous forme écrite. »;<br /><p style="margin: 0 2em; text-indent: 30px">➥ + du suffixe nominal de résultat‎ -μα / -ma [[wikt:en:-μα#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du suffixe adjectival -ῐκός / -ikós, de ou se rapportant à, de la manière de ; « -ique »;<br /><p style="margin: 0 2em; text-indent: 15px">Enseignant responsable de la deuxième étape du système éducatif traditionnel, après l’apprentissage de l’alphabet, la lecture et l’écriture, et l’initiation au calcul avec un abaque chez un ''magister ludi'' [[w:en:Ludi_magister|(en)]] et avant celle de l’art du discours chez un [[w:Rhétorique#Rhétorique_dans_l'Antiquité_grecque|''rhéteur'']]. Le travail du grammairien était d’enseigner la lecture, l’analyse de textes des poètes antiques tels qu’Homère, Tite-Live et Virgile, et la grammaire [https://archive.wikiwix.com/cache/index2.php?url=http%3A%2F%2Fwww.antiquite.ac-versailles.fr%2Feducatio%2Fedrom2.htm#federation=archive.wikiwix.com&tab=url {{Info|<sup>➕</sup>|« L’école du grammaticus », ac-Versailles}}] [https://philo-lettres.fr/latin/rome_vie-quotidienne/ecole-romaine/ {{Info|<sup>➕➕</sup>|« L’École dans l’antiquité romaine, philo-lettres}}]. <br /><br /><p style="margin: 0 2em; text-indent: 15px;">'''[[#polygraphe_back|<span id="polygraphe"><sup>III</sup></span>]] Du nom commun grec ancien [[wikt:polygraphe|πολύγραφος / polýgraphos]], « qui écrit beaucoup de sujets »;<br /><p style="margin: 0 2em; text-indent: 15px">➥ de l’adjectif πολῠ́ς / polús [[wikt:en:πολύς#Ancient_Greek|(en)]], « (de nombre, au pluriel) Beaucoup de : • (avec des noms de multitude) Grand ; • (de quantité, avec des noms de masse) beaucoup de ; • (rare, d'une personne) Grand, puissant ; • (de son) Fort ; • (attributivement, adverbial) Fortement, pleinement ; • (d'espace) Large, grand ; • (de distance) Loin ; • (de temps) Long, en retard. »;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du verbe γράφω / gráphō [[wikt:en:γράφω#Ancient_Greek|(en)]], « 1. (Homérique) égratigner, couper en dedans. 2. Dessiner, esquisser, peindre. 3. Écrire. 4. Écrire, proposer une loi. 5. (''voix moyenne'') : • (''réflexif indirect'') Écrire pour soi, noter ; • Accuser, poursuivre. 6. (''passif parfait'') Être écrit, être sous forme écrite. »;<br /><p style="margin: 0 2em; text-indent: 15px;">Le terme semble avoir été employé au cours de l’Antiquité dans un sens différent de celui que nous l’entendons aujourd’hui, pas dans le sens de diversité mais, de façon restreinte, le fait de composer un grand nombre de textes [https://eriac.univ-rouen.fr/la-polygraphie-comme-norme/ {{Info|<sup>➕</sup>|Isabelle Gassino, Université de Rouen et Dimitri Kasprzyk, université de Brest, « Colloque "La polygraphie comme norme" », 16 et 17 novembre 2017.}}].'''<br/><br/></div> {{Boîte déroulante fin}} ==== Livre I ==== ===== <div style="text-align: center;">Chapitre II.</div> ===== <div style="text-align: justify; margin: 0 1em;">Témoignage de '''Thales''' comme : * l’un des premiers ''philosophes grecs'' ayant traité des choses célestes et divines ; * disciple des ''Égyptiens'' et des ''Chaldéens'' (premier témoignage) ; * auteur de court(s)/rare(s) ouvrages (supposément unanimement admis), que les ''Grecs'' considéreraient comme les plus anciens, et douteraient, selon '''Flavius Josèphe''', de leur authenticité.</div> :'''Texte latin''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">qui autem historias apud eos conscribere temptauerunt, id est hi, qui circa '''Cadmum''' ''Milesium'', et '''Acusilaus''' ''Argiuus'' et post hunc quicumque alii fuisse referuntur, paululum tempus ''Persicam'' apud ''Helladium'' militiam praecesserunt. sed etiam eos, qui de caelestibus et diuinis primitus apud ''Graecos philosophati'' sund, id est '''Pherecydem''' ''Syrum'' et '''Pythagoram''' et '''Thaletem''' omnes concorditer confidentur ''Aegyptiorum'' et ''Chaldaeorum'' fuisse discipulos et breuiter conscripsisse quae a ''Graecis'' omnium antiquissima iudicantur ita ut uix ea credant ab illis fuisse conscripta.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[[w:Corpus_scriptorum_ecclesiasticorum_latinorum|<u>Corpus scriptorum ecclesiasticorum latinorum</u>]], [https://verlag.oeaw.ac.at/produkt/flavii-iosephi-opera-ex-versione-latina-antiqua-pars-vi-de-iudaeorum-vetustate-sive-contra-apionem-libri-ii/601067?name=flavii-iosephi-opera-ex-versione-latina-antiqua-pars-vi-de-iudaeorum-vetustate-sive-contra-apionem-libri-ii&product_form=5107 <u>Tome XXXVII, Flavius Iosephus, Contra Apionem</u>], ''Livre I'', ''chap. II.'', ''l.13, 14'', p.64, 1898</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">Οἱ μέντοι τὰς ἱστορίας ἐπιχειρήσαντες συγγράφειν παρ' αὐτοῖς, λέγω δὲ τοὺς περὶ '''Κάδμον''' τε τὸν ''Μιλήσιον'' καὶ τὸν ''Ἀργεῖον'' '''Ἀκουσίλαον''' καὶ μετὰ τοῦτον εἴ τινες ἄλλοι λέγονται γενέσθαι, βραχὺ τῆς ''Περσῶν'' ἐπὶ τὴν ''Ἑλλάδα'' στρατείας τῷ χρόνῳ προύλαβον. Ἀλλὰ μὴν καὶ τοὺς περὶ τῶν οὐρανίων τε καὶ θείων πρώτους παρ' ''Ἕλλησι φιλοσοφήσαντας'', οἷον '''Φερεκύδην''' τε τὸν ''Σύριον'' καὶ '''Πυθαγόραν''' καὶ '''Θάλητα''', πάντες συμφώνως ὁμολογοῦσιν ''Αἰγυπτίων'' καὶ ''Χαλδαίων'' γενομένους μαθητὰς ὀλίγα συγγράψαι, καὶ ταῦτα τοῖς ''Ἕλλησιν'' εἶναι δοκεῖ πάντων ἀρχαιότατα καὶ μόλις αὐτὰ πιστεύουσιν ὑπ' ἐκείνων γεγράφθαι.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://remacle.org/bloodwolf/historiens/Flajose/Apion1gr.htm <u>Φλαίίου Ἰωσήπου, περὶ ἀρχαιότητος Ἰουδαίων</u>, ''λόγος α''], ''chap. II.'', ''l.13, 14'', 1898</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">Quant aux Grecs qui ont entrepris d’écrire l’histoire, comme [[w:Cadmos_de_Milet|'''Cadmos''' de ''Milet'']], [[w:Acousilaos|'''Acousilaos''' d’Argos]] et ceux qu’on cite après lui, ils n’ont vécu que peu de temps [[#Cadmos_NdT_LB|<span id="Cadmos_NdT_LB_back"><sup>1</sup></span>]] avant [[w:Guerres_médiques|''l’expédition des Perses contre la Grèce'']]. Mais bien certainement les premiers ''philosophes grecs'' qui aient traité des choses célestes et divines, comme [[w:Phérécyde_de_Syros|'''Phérécyde''' de ''Syros'']] [[#Phérécyde_de_Syros_NdT_LB|<span id="Phérécyde_de_Syros_NdT_LB_back"><sup>2</sup></span>]], '''Pythagore''' et '''Thalès'''[[#Thales_NdT_LB|<span id="Thales_NdT_LB_back"><sup>3</sup></span>]] furent, tout le monde s’accorde là-dessus, les disciples des ''Égyptiens'' et des ''Chaldéens'' avant de composer leurs courts ouvrages, et ces écrits sont aux yeux des ''Grecs'' les plus anciens de tous ; à peine même les croient-ils authentiques.</div> <table cellspacing=15 style="margin: 0 4em; font-size:85%;"> <tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Cadmos_NdT_LB_back|<span id="Cadmos_NdT_LB"><sup>1.</sup></span>]] En réalité, Cadmos paraît avoir fleuri vers le milieu [[w:VIe_siècle_av._J.-C.|VI<sup>ème</sup> siècle]] [{{Info|AEC|Avant l’Ère Commune}}].''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Phérécyde_de_Syros_NdT_LB_back|<span id="Phérécyde_de_Syros_NdT_LB"><sup>2</sup></span>]] Seul texte qui attribue une origine égyptienne ou chaldéenne aux doctrines de Phérécyde de Syros. Cependant [[w:Theodor_Gomperz|Gompers]], [https://archive.org/details/bub_gb_QrfVAAAAMAAJ/page/n434/mode/1up Griechische Denker, I, 430], identifie ᾿Ογηνός avec l’Ouginna babylonien.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Thales_NdT_LB_back|<span id="Thales_NdT_LB"><sup>3</sup></span>]] On retrouve chez [[w:Apollonios_de_Tyane|Apollonios de Tyane]] ([[w:Jamblique|Jamblique]] [[#Jamblique_back|<sup>⤵️</sup>]], Vit. Pyth. [[#Vie_de_Pythagore_back|<sup>⤵️</sup>]], 12) et [[w:Plutarque|Plutarque]] [[#Plutarque_back|<sup>⤵️</sup>]] l’idée que Thalès de Milet fut disciple des Égyptiens ; l’adjonction des Chaldéens est propre Josèphe.''' </td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/contreapiontexte0000jose/page/n6/mode/1up?view=theater <u>Flavius Josèphe, Contre Apion</u>], ''Livre I'', [https://archive.org/details/contreapiontexte0000jose/page/n49/mode/1up?view=theater&q=Thales ''chap. 2''], traduction du grec ancien par Léon Blum, agrégé des Lettres, professeur au lycée Janson-de-Sailly, texte établi et annotée par [[w:Théodore_Reinach|Théodore Reinach]] Membre de l’[[w:Institut_de_France|Institut]], professeur au Collège de France, 1930<br />(édition bilingue de 1911 également disponible [https://remacle.org/bloodwolf/historiens/Flajose/Apion1.htm ici])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-align: justify; margin: 0 2em;">Quant aux Grecs qui ont entrepris d’écrire l’histoire, comme '''Cadmos''' de ''Milet'', '''Acousilaos''' d’''Argos'' et ceux qu’on nomme après lui, ils n’ont vécu que peu de temps[2] avant l’expédition des ''Perses'' contre la ''Grèce''. [14]. De même, les premiers ''philosophes grecs'' qui aient traité des choses célestes et divines, comme '''Phérécyde''' de ''Syros''[3], '''Pythagore''' et '''Thalès'''[4] furent, tout le monde s’accorde là dessus, les disciples des ''Égyptiens'' et des ''Chaldéens'' avant de composer leurs rares ouvrages, et ces écrits sont aux yeux des ''Grecs'' les plus anciens de tous ; à peine même les croient-ils authentiques.</div> <table cellspacing=15 style="margin: 0 4em; font-size:85%;"> <tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Cadmos_NdT_LB_back|<span id="Cadmos_NdT_LB"><sup>1.</sup></span>]] En réalité, Cadmos paraît avoir fleuri vers le milieu [[w:VIe_siècle_av._J.-C.|VI<sup>ème</sup> siècle]] [{{Info|AEC|Avant l’Ère Commune}}].''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Phérécyde_de_Syros_NdT_LB_back|<span id="Phérécyde_de_Syros_NdT_LB"><sup>2</sup></span>]] Seul texte qui attribue une origine égyptienne ou chaldéenne aux doctrines de Phérécyde de Syros. Cependant [[w:Theodor_Gomperz|Gompers]], [https://archive.org/details/bub_gb_QrfVAAAAMAAJ/page/n434/mode/1up Griechische Denker, I, 430], identifie ᾿Ογηνός avec l’Ouginna babylonien.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Thales_NdT_LB_back|<span id="Thales_NdT_LB"><sup>3</sup></span>]] On retrouve chez [[w:Apollonios_de_Tyane|Apollonios de Tyane]] ([[w:Jamblique|Jamblique]] [[#Jamblique_back|<sup>⤵️</sup>]], Vit. Pyth. [[#Vie_de_Pythagore_back|<sup>⤵️</sup>]], 12) et [[w:Plutarque|Plutarque]] [[#Plutarque_back|<sup>⤵️</sup>]] l’idée que Thalès de Milet fut disciple des Égyptiens ; l’adjonction des Chaldéens est propre Josèphe.''' </td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Flavius Josèphe, De l’ancienneté du peuple juif (Contre Apion)</u>, ''Livre I'', [[s:Page:Flavius_Josephe_-_Leon_Blum_-_Contre_Apion,_Leroux,_Paris,_1902.djvu/17|''Chap. 2'']], traduction de Léon Blum, agrégé des lettres, professeur au lycée du Havre, sous la direction de [[w:Théodore_Reinach|Théodore Reinach]], 1902.</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div> </div> <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Plutarque|'''Plutarque''']] [[#Plutarque|<span id="Plutarque_back"><sup>'''I'''</sup></span>]] == <p style="text-align: right;">([[w:Circa|{{Info|''ca.''|Circa, locution latine que l’on emploie pour indiquer l’approximation d’une date}}]] [[w:45|45]]'' <sup>[[w:Ier_siècle|⏳]]</sup>'', à [[w:Chéronée|Chéronée]] en [[w:Béotie|Béotie]] — {{Info|''ca.''|Circa, locution latine que l’on emploie pour indiquer l’approximation d’une date}} [[w:125|125]]'' <sup>[[w:IIe_siècle|⏳]]</sup>)[[s:Auteur:Plutarque|<sup>📚</sup>]] [[Fichier:Plutarch at Delphi.jpg|vignette|<p style="text-align: justify; text-indent: 15px;">Buste probable de Plutarque du [[w:IIe_siècle|II<sup>ème</sup>]] ou [[w:IIIe_siècle|III<sup>ème</sup>]] siècles {{Info|EC|de l’Ère Commune}}, en marbre de [[w:Marbre_de_Paros|''Paros'']].<br /><p style="text-align: justify; text-indent: 15px;">Provenance : découvert lors de fouilles près de l’angle sud-est du [[w:Temple d'Apollon (Delphes)|''temple d’Apollon'']] de [[w:Delphes|''Delphes'']], au côté d'une [[w:Fichier:Plutarch_stele_inscription_100_AD,_AM_of_Delphi_4070060092.jpg|''stèle'']] portant une inscription gravée : ΔΕΛΦΟΙ ΧΑΙΡΩΝΕΥΣΙΝ ΟΜΟΥ ΠΛΟΥΤΑΡΧΟΝ ΕΘΗΚΑΝ ΤΟΙΣ ΑΜΦΙΚΤΥΟΝΩΝ ΔΟΓΜΑΣΙ ΠΕΙΘΟΜΕΝΟΙ — Les ''Delphiens'', avec les ''Chéronéens'', dédièrent ce(tte image de) Plutarque, suivant les préceptes de l’[[w:Amphictyonie|''Amphictyonie'']].<br /><p style="text-align: justify; text-indent: 15px;">Exposition : Salle XIV, [[w:Mus%C3%A9e_arch%C3%A9ologique_de_Delphes|''Musée archéologique de Delphes'']].]] <div style="text-align: justify; margin: 0 1em; text-indent: 15px">Biographe, philosophe et moraliste grec, auteur d’une œuvre importante, comportant un ensemble varié de traités et de dialogues consacrés à des questions de philosophie morale, mais abordant aussi des sujets littéraires, politiques, scientifiques, religieux.</div> {{Boîte déroulante début|titre=NdA Plutarque|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Plutarque_back|<span id="Plutarque"><sup>I</sup></span>]] Du nom propre grec ancien πλούταρχος / ploútarkhos [[wikt:en:Πλούταρχος#Ancient_Greek|(en)]], « maître des richesses » ; <br /><p style="margin: 0 2em; text-indent: 15px">➥ du nom commun πλοῦτος / ploûtos [[wikt:en:πλοῦτος#Ancient_Greek|(en)]], « richesses » ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du nom commun ἀρχός / arkhós, « souverain, chef, prince »)'''<br/><br/></div> {{Boîte déroulante fin}} {{Boîte déroulante début|titre=Remarque|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>''' Dans la ''Vie de [[w:Lycurgue_(législateur)|Lycurgue]]'' et la ''Vie d’Agis et Cléomène'' (''Vies parallèles''), et le traité ''Un philosophe doit surtout converser avec les princes'' (''Œuvres morales''), il est fait mention d’un Thalès : il s’agit de [[w:Thalétas|Thalétas]], aussi appelé Thalès de [[w:Crète|''Crète'']], un musicien et poète, originaire de la cité de [[w:Gortyne|''Gortyne'']] en ''Crète'', et actif à la fin du [[w:VIIIe_siècle_av._J.-C.|VIII<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]] et au début du siècle suivant.'''<br/><br/></div> {{Boîte déroulante fin}} === [[w:Vies_parallèles|Vies parallèles]] [[#Vies_parallèles|<span id="Vies_parallèles_back"><sup>'''I'''</sup></span>]] === <div style="text-align: center; margin: 0 1em;">Recueil de 50 biographies de grands hommes de l’histoire, dont 46 présentées par paires : un ''Grec'' mis en parallèle avec un ''Romain''.</div> {{Boîte déroulante début|titre=NdA Vies parallèles|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Vies_parallèles_back|<span id="Vies_parallèles"><sup>I</sup></span>]] Du grec ancien Βίοι Παράλληλοι / Bíoi Parállêloi'''<br/><br/></div> {{Boîte déroulante fin}} ==== Vie de [[w:Solon|'''Solon''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Solon|<sup>🔄</sup>]] ==== <div style="text-align: center; margin: 0 1em;">Biographie de '''Solon''', qui précède celle de [[w:Publius_Valerius_Publicola_(consul_en_-509)|'''Publicola''']] [[#Publicola|<span id="Publicola_back"><sup>'''I'''</sup></span>]], et avec laquelle '''Plutarque''' la compare.</div> {{Boîte déroulante début|titre=NdA Vie de Solon|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Publicola_back|<span id="Publicola"><sup>I</sup></span>]] [[w:Nom_romain|Tria Nomina]] en latin {{Info|Publius|praenomen, nom individuel du citoyen romain}} {{Info|Valerius|nomen, nom de famille}} {{Info|Publicola ou Poplicola|cognomen, surnom héréditaire, servant à distinguer les diverses branches d’une même gens}}, « celui qui prend soin de l’armée. »[https://academic.oup.com/bics/advance-article/doi/10.1093/bics/qbaf002/8117642?login=false {{Info|<sup>🔍</sup>|M. Gallo, « Misinterpreting a compound name. The origin of the agnomen Publicola in Dionysius of Halicarnassus and Plutarch », Bulletin of the Institute of Classical Studies,‎ 22 avril 2025}}].<br /><p style="margin: 0 2em; text-indent: 15px">[[w:Consul_(Rome_antique)|Consul]] de la [[w:république_romaine|''République Romaine'']], à quatre reprises : en [[w:-509|-509]], [[w:-508|-508]], [[w:-507|-507]] et [[w:-504|-504]], et l’un des instaurateurs légendaires de la ''République Romaine'' en -509, suite au viol et au suicide de [[w:Lucrèce_(dame_romaine)|Lucrèce]], une femme aristocratique ''romaine'', par [[w:Sextus_Tarquin|Sextus Tarquin]], le fils du dernier [[w:Roi_de_Rome|roi de ''Rome'']] [[w:Tarquin_le_Superbe|Tarquin le Superbe]].<br /><p style="text-align: right; margin: 0 2em;">(date et lieu de naissance inconnu.e.s — [[w:-503|-503]], soit sur le champ de bataille pendant les [[w:Guerres_romano-sabelliennes|''guerres romano-sabelliennes'']], soit de maladie)<sup>[[w:VIe_siècle_av._J.-C.|⏳]]</sup> '''<br/><br/></div> {{Boîte déroulante fin}} ===== <div style="text-align: center;">Chapitre II.</div> ===== ====== <div style="text-align: center;">Paragraphe III.</div> ====== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de '''Thalès''' ''commerçant''</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''III.''' Κωλύει δὲ οὐδὲν τὸν ἀγαθὸν καὶ πολιτικὸν ἄνδρα μήτε τῶν περιττῶν τὴν κτῆσιν ἐν σπουδῇ τίθεσθαι μήτε τῆς χρείας τῶν ἀναγκαίων καὶ ἱκανῶν καταφρονεῖν. Ἐν δὲ τοῖς τότε χρόνοις, καθ' Ἡσίοδον, ἔργον οὐδὲν ἦν ὄνειδος, οὐδὲ τέχνη διαφορὰν ἔφερεν, ἐμπορία δὲ καὶ δόξαν εἶχεν οἰκειουμένη τὰ βαρβαρικὰ καὶ προξενοῦσα φιλίας βασιλέων καὶ πραγμάτων ἐμπείρους ποιοῦσα πολλῶν. Ἔνιοι δὲ καὶ πόλεων οἰκισταὶ γεγόνασι μεγάλων, ὡς καὶ Μασσαλίας Πρῶτις ὑπὸ Κελτῶν τῶν περὶ τὸν Ῥοδανὸν ἀγαπηθείς. Καὶ Θαλῆν δέ φασιν ἐμπορίᾳ χρήσασθαι καὶ Ἱπποκράτην τὸν μαθηματικόν, καὶ Πλάτωνι τῆς ἀποδημίας ἐφόδιον ἐλαίου τινὸς ἐν Αἰγύπτῳ διάθεσιν γενέσθαι.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm <u>Plutarque, Vie de Solon</u>], ''chap. II'', ''§3'', traduction par [[w:Dominique_Ricard|Dominique Ricard]], 1844<br />(également disponible une édition de 1853 [https://remacle.org/bloodwolf/historiens/Plutarque/solonpierrron.htm ici] et de 1862 [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f16.item là])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 390px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''III.''' Mais rien n’empêche l’homme de bien, le citoyen dévoué à son pays, de garder un juste milieu : il peut ne point s’attacher à la poursuite du superflu, sans pour cela mépriser le nécessaire et ce qui suffit à ses besoins.<br />Dans ce temps-là, pour parler comme [[w:Hésiode|'''Hésiode''']] [[#Hésiode|<span id="Hésiode_back"><sup>'''I'''</sup></span>]] [[#Hésiode_NdT_AP|<span id="Hésiode_NdT_AP_back"><sup>1</sup></span>]], il n’y avait pas de travail qui fût honteux; aucun art ne mettait de différence entre les hommes : le négoce surtout était honoré, qui met en possession des avantages dont jouissent les étrangers, gagne l’amitié des rois, et donne une grande expérience. On a même vu des trafiquants fonder de grandes villes : ainsi [[w:Mythe_fondateur_de_Marseille|'''Protis''']] bâtit [[w:Marseille_antique|''Marseille'']], après s'être concilié l’amitié des ''Gaulois'' qui habitent les bords du [[w:Rhône#Histoire|''Rhône'']]. '''Thales''' se livra, dit-on, au négoce, ainsi qu’[[w:Hippocrate_de_Chios|'''Hippocrate''']] [[#Hippocrate|<span id="Hippocrate_back"><sup>'''II'''</sup></span>]] le mathématicien[[#Hippocrate_NdT_AP|<span id="Hippocrate_NdT_AP_back"><sup>2</sup></span>]] ; et [[w:Platon|'''Platon''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Platon|<sup>🔄</sup>]] vendit de l’huile en [[w:Basse_Époque|''Égypte'']] , pour fournir aux frais de son voyage.</div> <table cellspacing=15 style="margin: 0 4em; font-size:85%;"> <tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Hésiode_NdT_AP_back|<span id="Hésiode_NdT_AP"><sup>1.</sup></span>]] Œuvres et Jours, vers 309.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Hippocrate_NdT_AP_back|<span id="Hippocrate_NdT_AP"><sup>2</sup></span>]] Cet Hippocrate n’est point connu d’ailleurs.''' </td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/viesdeshommesill01plut/page/n6/mode/1up?view=theater <u>Vie des Hommes Illustres de Plutarque</u>], [https://archive.org/details/viesdeshommesill01plut/page/182/mode/1up?view=theater ''Solon''], ''Chap. II'', ''§3'', [https://archive.org/details/viesdeshommesill01plut/page/184/mode/1up?view=theater p.184], traduit par [[w:Alexis_Pierron|Alexis Pierron]], professeur au lycée Louis-le-Grand, 1877<br />(également disponible une édition de 1853 [[s:Vies_des_hommes_illustres/Solon|ici]])</div> {{Boîte déroulante début|titre=NdA trad. par Alexis Pierron de 1877|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Hésiode_back|<span id="Hésiode"><sup>I</sup></span>]] Du nom propre grec ancien Ἡσῐ́οδος / Hēsĭ́odos [[wikt:en:Ἡσίοδος#Ancient_Greek|(en)]].<br /><p style="margin: 0 2em; text-indent: 15px">Berger sur les pentes du Mont [[w:Mont_Hélicon|Hélicon]] et un des plus grands poètes grecs. Il s’agit plus précisément d’un ''aède'' (il « chante » ses vers avec sa lyre) et un ''rhapsode'' (il « coud » des chants entre eux)[https://odysseum.eduscol.education.fr/hesiode-un-des-premiers-poetes-grecs {{Info|<sup>🔍</sup>|Hésiode, un des premiers poètes grecs - Odysseum, la maison numérique des Humanités}}]. <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Hippocrate_back|<span id="Hippocrate"><sup>II</sup></span>]] Du nom propre grec ancien Ῐ̔πποκρᾰ́της / Hĭppokrắtēs [[wikt:en:Ἱπποκράτης#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">➥ du nom commun ῐ̔́ππος / hĭ́ppos [[wikt:en:ἵππος#Ancient_Greek|(en)]], « cheval »;<br /><p style="margin: 0 2em; text-indent: 15px">➥ + du nom commun‎ κρᾰ́τος / krắtos [[wikt:en:κράτος#Ancient_Greek|(en)]], « 1. Puissance, force. 2. Acte de force, acte de bravoure. 3. (au pluriel) Actes de violence. 4. Domination, pouvoir. »;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du suffixe nominal -ης / -ēs.<br /><p style="margin: 0 2em; text-indent: 15px"> Mathématicien (géomètre) et astronome « para-pythagoricien », dont l’œuvre ne nous est pas parvenue.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] milieu du [[w:Ve_siècle_av._J.-C.|V<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]]) [https://books.google.fr/books?id=DrvWAAAAMAAJ&newbks=1&newbks_redir=0&lpg=PA764&vq=Hippocrate&dq=bibliogroup%3A%22Dictionnaire%20des%20philosophes%20antiques%22&hl=fr&pg=PA764#v=onepage&q&f=true {{Info|<sup>🔍</sup>|Dictionnaire des Philosophes Antiques, publié sous la direction de Richard Goulet, Volume III, §151 - Hippocrate de Chios}}]'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: center; margin: 0 2em;">'''II. Dans sa jeunesse il se livre au commerce maritime; la modicité de sa fortune et son goût pour la sagesse l’y décident. Faveur du commerce à cette époque ; grands noms qui l’ont illustré.'''<br /><br /><p style="text-align: justify; text-indent: 15px;">Mais rien n’empêche qu’un homme de bien, un sage politique tienne à cet égard un juste milieu, et que sans rechercher des richesses superflues, il ne méprise pas celles qui sont nécessaires et qui suffisent. Dans ce temps-là, comme dit [[w:Hésiode|'''Hésiode''']], aucun travail n’était regardé comme honteux; aucun art ne mettait de différence entre les hommes. Le commerce maritime surtout était honorable; il ouvrait des communications utiles avec les nations étrangères, procurait des alliances avec les rois, et donnait une grande expérience. On a même vu des commerçants fonder de grandes villes. Ainsi '''Protus''' gagna l’amitié des Gaulois qui habitaient les bords du ''Rhône'', et bâtit ''Marseille''. '''Thalès''' et '''Hippocrate''' le mathématicien firent aussi le commerce ; et '''Platon''' vendit de l’huile en ''Égypte'' pour fournir aux frais de son voyage.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f16.item ''chap. II''], traduction correcte et précédée du texte grec, par une société de professeurs et d’helléniste, 1862<br />(également disponible une édition de 1844 [https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm#1a ici] et de 1829 [[s:Les_Vies_des_hommes_illustres/Vie_de_Solon|là]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">Mais rien n’empêche l’homme de-bien et l’homme politique ni se mettre en souci (rechercher) l’acquisition des choses superflues, ni mépriser l’usage des choses nécessaires et suffisantes. Or dans les temps d’alors, selon '''Hésiode''', aucun travail n’était sujet-de-honte, ni aucun métier n’apportait de différence entre les citoyens mais même le commerce-maritime avait de la gloire, rendant-amies les nations-barbares, et procurant des amitiés de rois, et faisant les hommes expérimentés d’affaires nombreuses. Et quelques-uns aussi sont devenus fondateurs de grandes villes, comme aussi le '''Protus''' de ''Marseille'' ayant été aimé par les ''Celtes'' ceux autour du ''Rhône''. Et on dit aussi '''Thalès''', avoir fait-usage du commerce-maritime et '''Hippocrate''' le mathématicien, et la vente d'une certaine huile en ''Égypte'' avoir été pour '''Platon''' ressource du voyage.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f16.item ''chap. II''], traduction littérale et juxtalinéaire présentant le mot à mot français en regard des mots grecs correspondants, par une société de professeurs et d’helléniste, 1862</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ====== <div style="text-align: center;">Paragraphe IV.</div> ====== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de la sagesse de '''Thalès''' en ''philosophie naturelle''</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''IV.''' Φιλοσοφίας δὲ τοῦ ἠθικοῦ μάλιστα τὸ πολιτικόν, ὥσπερ οἱ πλεῖστοι τῶν σοφῶν, ἠγάπησεν. Ἐν δὲ τοῖς φυσικοῖς ἁπλοῦς ἐστι λίαν καὶ ἀρχαῖος, ὡς δῆλον ἐκ τούτων· [...].<br /><p style="text-indent: 15px">Καὶ ὅλως ἔοικεν ἡ Θάλεω μόνου σοφία τότε περαιτέρω τῆς χρείας ἐξικέσθαι τῇ θεωρίᾳ· τοῖς δὲ ἄλλοις ἀπὸ τῆς πολιτικῆς ἀρετῆς τοὔνομα τῆς σοφίας ὑπῆρξε.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm <u>Plutarque, Vie de Solon</u>], ''chap. II'', ''§4'', traduction par [[w:Dominique_Ricard|Dominique Ricard]], 1844<br />(également disponible une édition de 1853 [https://remacle.org/bloodwolf/historiens/Plutarque/solonpierrron.htm ici] et de 1862 [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f20.item là])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 390px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''IV.''' Il s’attacha, comme presque tous les sages d’alors, à cette partie de la philosophie morale qui traite de la politique. Pour la philosophie naturelle, il en était aux rudiments, et aux notions du vieux temps sans plus; [...].<br /><p style="text-indent: 15px;">Aussi bien n’y eut-il, en somme, que '''Thalès''' dont la science dépassât alors les notions d’un usage vulgaire : tous les autres ne durent qu’à leurs connaissances politiques leur réputation de sagesse.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/viesdeshommesill01plut/page/n6/mode/1up?view=theater <u>Vie des Hommes Illustres de Plutarque</u>], [https://archive.org/details/viesdeshommesill01plut/page/182/mode/1up?view=theater ''Solon''], ''Chap. II'', ''§4'', [https://archive.org/details/viesdeshommesill01plut/page/185/mode/1up?view=theater p.185], traduit par [[w:Alexis_Pierron|Alexis Pierron]], professeur au lycée Louis-le-Grand, 1877<br />(également disponible une édition de 1853 [[s:Vies_des_hommes_illustres/Solon|ici]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">A l’exemple des sages de son temps, il cultiva principalement cette partie de la morale qui traite de la politique. Il n’avait en physique que des connaissances très-superficielles, et en était aux premiers éléments de cette science, [...].<br/><p style="text-indent: 15px">En général '''Thalès''' fut, de tous les sages d’alors, le seul qui porta au delà des besoins de la vie la théorie des sciences ; tous les autres ne durent qu’à leurs connaissances politiques leur réputation de sagesse.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f20.item ''chap. II''], traduction correcte et précédée du texte grec, par une société de professeurs et d’helléniste, 1862<br />(également disponible une édition de 1844 [https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm#1a ici] et de 1829 [[s:Les_Vies_des_hommes_illustres/Vie_de_Solon|là]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">Mais de la partie morale de la philosophie il embrassa surtout la partie politique, comme la plupart des sages d’alors. Mais dans les sciences physiques il est extrêmement simple (ignorant) et primitif, [...]. Et en-un-mot la science de '''Thalès''' seul paraît s’être avancée alors par la théorie plus loin que le besoin ; et le nom de la science a appartenu aux autres par-suite des qualités politiques.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f21.item ''chap. II''], traduction littérale et juxtalinéaire présentant le mot à mot français en regard des mots grecs correspondants, par une société de professeurs et d’helléniste, 1862</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ====== <div style="text-align: center;">Paragraphe V.</div> ====== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Version alternative du récit de la coupe de [[w:Bathyclès_de_Magnésie|'''Bathyclès''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Bathyclès_back|<sup>🔄</sup>]] par [[w:Callimaque_de_Cyrène|'''Callimaque''']] de [[w:Cyrène|''Cyrène'']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Callimaque_back|<sup>🔄</sup>]].</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''V.''' Γενέσθαι δὲ μετ' ἀλλήλων ἔν τε Δελφοῖς ὁμοῦ λέγονται καὶ πάλιν ἐν Κορίνθῳ, Περιάνδρου σύλλογόν τινα κοινὸν αὐτῶν καὶ συμπόσιον κατασκευάσαντος. Ἔτι δὲ μᾶλλον εἰς ἀξίωμα καὶ δόξαν αὐτοὺς κατέστησεν ἡ τοῦ [[w:τρίπους#Grec_ancien|'''τρίποδος''']] περίοδος καὶ διὰ πάντων ἀνακύκλησις καὶ ἀνθύπειξις μετ' εὐμενείας φιλοτίμου γενομένη. Κῴων γάρ, ὥς φασι, καταγόντων σαγήνην, καὶ ξένων ἐκ Μιλήτου πριαμένων τὸν βόλον οὔπω φανερὸν ὄντα, χρυσοῦς ἐφάνη τρίπους ἑλκόμενος, ὃν λέγουσιν Ἑλένην πλέουσαν ἐκ Τροίας αὐτόθι καθεῖναι χρησμοῦ τινος ἀναμνησθεῖσαν παλαιοῦ. Γενομένης δὲ τοῖς ξένοις πρῶτον ἀντιλογίας πρὸς τοὺς ἁλιέας περὶ τοῦ τρίποδος, εἶτα τῶν πόλεων ἀναδεξαμένων τὴν διαφορὰν ἄχρι πολέμου προελθοῦσαν, ἀνεῖλεν ἀμφοτέροις ἡ Πυθία τῷ σοφωτάτῳ τὸν τρίποδα ἀποδοῦναι. Καὶ πρῶτον μὲν ἀπεστάλη πρὸς Θαλῆν εἰς Μίλητον, ἑκουσίως τῶν Κῴων ἑνὶ δωρουμένων ἐκείνῳ περὶ οὗ πρὸς ἅπαντας ὁμοῦ Μιλησίους ἐπολέμησαν. Θάλεω δὲ Βίαντα σοφώτερον ἀποφαίνοντος αὑτοῦ πρὸς ἐκεῖνον ἧκεν· ἀπ' ἐκείνου δ' αὖθις ἀπεστάλη πρὸς ἄλλον ὡς σοφώτερον. Εἶτα περιϊὼν καὶ ἀναπεμπόμενος οὕτως ἐπὶ Θαλῆν τὸ δεύτερον ἀφίκετο, καὶ τέλος εἰς Θήβας ἐκ Μιλήτου κομισθεὶς τῷ Ἰσμηνίῳ Ἀπόλλωνι καθιερώθη. Θεόφραστος δέ φησι,πρῶτον μὲν εἰς Πριήνην Βίαντι τὸν τρίποδα πεμφθῆναι, δεύτερον δ' εἰς Μίλητον Θαλῇ Βίαντος ἀποπέμψαντος· οὕτω δὲ διὰ πάντων πάλιν εἰς Βίαντα περιελθεῖν, τέλος δὲ εἰς Δελφοὺς ἀποσταλῆναι. Ταῦτα μὲν οὖν ὑπὸ πλειόνων τεθρύληται, πλὴν ὅτι τὸ δῶρον ἀντὶ τοῦ τρίποδος οἱ μὲν φιάλην ὑπὸ Κροίσου πεμφθεῖσαν, οἱ δὲ ποτήριον Βαθυκλέους ἀπολιπόντος εἶναι λέγουσιν.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm <u>Plutarque, Vie de Solon</u>], ''chap. II'', ''§5'', traduction par [[w:Dominique_Ricard|Dominique Ricard]], 1844<br />(également disponible une édition de 1853 [https://remacle.org/bloodwolf/historiens/Plutarque/solonpierrron.htm ici] et de 1862 [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f22.item là])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 390px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''V.''' On raconte que les sept sages se réunirent une fois à [[w:Delphes|''Delphes'']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Delphes_back|<sup>🔄</sup>]], et une autre fois à [[w:Histoire_de_Corinthe_dans_l%27Antiquité|''Corinthe'']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Corinthe_back|<sup>🔄</sup>]], où [[w:Périandre|'''Périandre''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Périandre_back|<sup>🔄</sup>]] les avait convoqués pour leur offrir un banquet. Rien ne contribua tant à leur réputation et à leur gloire, que le renvoi qu’ils se firent successivement l’un à l’autre du trépied d’or, et l’honorable humilité avec laquelle ils refusèrent le prix tour à tour. Des hommes de [[w:Kos_(Dodécanèse)#Kos_à_l'époque_hellénistique|''Cos'']] [[#Cos|<span id="Cos_back"><sup>'''I'''</sup></span>]] venaient, dit-on, de jeter leur filet en mer : des étrangers de ''Milet'' achetèrent le coup, avant que les pêcheurs y eussent regardé. Il se trouva, dans le filet, un trépied d’or qu’[[w:Hélène_(mythologie)|'''Hélène''']] [[#Hélène|<span id="Hélène_back"><sup>'''II'''</sup></span>]], à ce qu’on prétend, pour obéir à un ancien oracle, avait jeté dans la mer à son retour de [[w:Troie|''Troie'']] [[#Troie|<span id="Troie_back"><sup>'''III'''</sup></span>]]. Ce fut un sujet de débat, d’abord entre les pêcheurs et les étrangers, ensuite entre les deux villes, qui prirent parti dans la querelle : la guerre allait s’allumer, lorsque la [[w:Pythie|''Pythie'']] [[#Pythie|<span id="Pythie_back"><sup>'''IV'''</sup></span>]], que les deux partis avaient consultée, commanda de donner le trépied au plus sage. On l’envoya d’abord à ''Milet'', pour '''Thalès''', et ceux de ''Cos'' cédèrent sans peine à un seul particulier ce qu’ils allaient disputer par les armes à tous les ''Milésiens'' ensemble. '''Thalès''' déclara que [[w:Bias_de_Priène|'''Bias''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Bias_back|<sup>🔄</sup>]] était plus sage que lui, et le lui fit passer. '''Bias''', avec la même modestie, le fit passer à un autre; et le trépied, après avoir été envoyé successivement à tous les sept, revint une seconde fois à '''Thalès'''. Enfin, il fut transporté de ''Milet'' à [[w:Thèbes_(Grèce)|''Thèbes'']] [[#Thèbes|<span id="Thèbes_back"><sup>'''V'''</sup></span>]], et consacré à Apollon [[w:en:Ismenus|''Isménien'']]. Cependant [[w:Théophaste|'''Théophaste''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Théophaste_back|<sup>🔄</sup>]] dit qu’on envoya le trépied d’abord à '''Bias''', dans ''Priène'' ; que '''Bias''' le fit porter à '''Thalès''' ; qu’après avoir passé alternativement chez tous les sages, il revint à '''Bias''', et qu’il finit par être envoyé à ''Delphes''. Telle est la tradition commune : seulement quelques-uns prétendent qu’il s’agissait de décerner non point un trépied, mais un vase que [[w:Crésus|'''Crésus''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Crésus_back|<sup>🔄</sup>]] avait envoyé ; et, suivant d’autres, c’était une coupe, héritage de '''Bathyclès'''.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/viesdeshommesill01plut/page/n6/mode/1up?view=theater <u>Vie des Hommes Illustres de Plutarque</u>], [https://archive.org/details/viesdeshommesill01plut/page/182/mode/1up?view=theater ''Solon''], ''Chap. II'', ''§5'', [https://archive.org/details/viesdeshommesill01plut/page/185/mode/1up?view=theater p.185], traduit par [[w:Alexis_Pierron|Alexis Pierron]], professeur au lycée Louis-le-Grand, 1877<br />(également disponible une édition de 1853 [[s:Vies_des_hommes_illustres/Solon|ici]])</div> {{Boîte déroulante début|titre=NdA trad. par Alexis Pierron de 1877|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Cos_back|<span id="Cos"><sup>I</sup></span>]] Du nom propre grec ancien Κῶς / Kôs [[wikt:en:Κῶς#Ancient_Greek|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px">Île grecque l’archipel du [[w:Dodécanèse|''Dodécanèse'']], au Sud-Est de la [[w:Mer_Égée|''mer Égée'']], au large des côtes ''turques''. <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Hélène_back|<span id="Hélène"><sup>II</sup></span>]] Du nom propre grec ancien Ἑλένη / Helénē [[wikt:en:Ἑλένη#Ancient_Greek|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px">Fille de [[w:Zeus|Zeus]] et de [[w:Léda (mythologie)|Léda]], considérée comme la plus belle femme du monde, uniquement surpassée par la déesse [[w:Aphrodite|Aphrodite]]. Elle est mariée à [[w:Ménélas|Ménélas]], roi de [[w:Sparte|Sparte]], et est enlevée par [[w:Pâris|Pâris]], prince [[w:Troie|''troyen'']] [[#Troie|<sup>'''III'''</sup>]], ce qui déclencha la [[w:guerre de Troie|guerre de Troie]] qui opposa ''Grecs'' et ''Troyens''. <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Troie_back|<span id="Troie"><sup>III</sup></span>]] Du nom propre grec ancien Τροίᾱ / Troíā [[wikt:en:Τροία#Ancient_Greek|(en)]] ; [[w:Troie#Toponymie|''toponymie incertaine'']] ;<br /><p style="margin: 0 2em; text-indent: 15px"> Cité semi-légendaire, située sur la colline d’''[[w:site archéologique de Troie|{{lang|tr|Hisarlık}}]]'', à l’entrée de l’''[[w:Hellespont|Hellespont]]'', non loin de la ''[[w:mer Égée|mer Égée]]'', au nord-ouest de la ''péninsule anatolienne'', dans la région ''[[w:Troade|Troade]]'' en ''[[w:Asie Mineure|Asie Mineure]]''.<br /><p style="margin: 0 2em; text-indent: 15px"> Le site a fait l’objet de nombreuses campagnes de fouilles, à la suite de celles entreprises par [[w:Heinrich Schliemann|Heinrich Schliemann]] en 1870, ponctuées par des découvertes fortement médiatisées, qui ont popularisé son identification avec la Troie homérique (qui reste un sujet de débat en l’absence de preuve décisive), le lieu principal des événements du ''[[w:cycle troyen|cycle troyen]]'' rapportés dans les ''[[w:Épopée|poèmes épiques]]'' ''[[w:Homère|homériques]]'' l’''[[w:Iliade|Iliade]]'' et l’''[[w:Odyssée|Odyssée]]''. <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Pythie_back|<span id="Pythie"><sup>IV</sup></span>]] Du nom commun grec ancien Πῡθῐ́ᾱ / Pūthĭ́ā,<br /><p style="margin: 0 2em; text-indent: 15px">➥ du nom propre Πῡθώ / Pūthṓ, « [[w:Delphes#Histoire_du_site|''Pythô'']], ancien nom de ''Delphes'' » ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du suffixe adjectival féminin -ῐος / -ĭos [[wikt:en:-ιος#Ancient_Greek|(en)]], « relatif à, appartenant à (de) ».<br /><p style="margin: 0 2em; text-indent: 15px">[[w:Divination_dans_la_Grèce_antique|''Oracle'']] du [[w:Temple_d%27Apollon_(Delphes)|''temple d’Apollon'']] à ''Delphes''.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:XIVe_siècle_av._J.-C.|XIV<sup>ème</sup>]]/[[w:VIIIe_siècle_av._J.-C.|VIII<sup>ème</sup>]] siècles {{Info|AEC|Avant l’Ère Commune}} — [[w:IVe_siècle_av._J.-C.|IV<sup>ème</sup> siècle {{Info|EC|de l’Ère Commune}}]]) <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Thèbes_back|<span id="Thèbes"><sup>V</sup></span>]] Du nom propre grec ancien Θῆβαι / Thêbai [[w:Θῆβαι#Ancient_Greek|(en)]], désigne indistinctement la cité ''grecque'' comme [[w:Thèbes_(Égypte)|celle ''égyptienne'']] ; mais leur étymologie diffère : pour celle de la cité grecque, de l’[[w:Ionien-attique|''ionien-attique'']] Θήβη / Thḗbē ; du grec mycénien 𐀳𐀣 / te-qa (/Tʰēgʷā/) ;<br /><p style="margin: 0 2em; text-indent: 15px">Cité grecque de la région de [[w:Béotie#Antiquité|''Béotie'']], au centre de la ''Grèce''.'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">On raconte que les sept sages se trouvèrent un jour ensemble à ''Delphes'', et une autre fois à ''Corinthe'', chez '''Périandre''', qui les avait réunis pour un banquet. Rien ne contribua autant à leur réputation et à leur gloire, que la modestie empressée avec laquelle ils se renvoyèrent l’un à l’autre le trépied d’or. Des ''Milésiens'' qui se trouvaient à l’île de ''Cos'', avaient acheté d’avance de quelques pêcheurs ce que retirerait de l’eau le filet qu’ils allaient y jeter. Quand on l’eut tiré, il s’y trouva un trépied d’or qu’'''Hélène''', à ce qu’on prétend, pour obéir à un oracle, avait jeté dans la mer, à son retour de ''Troie''. Cet incident donna lieu à une vive dispute d’abord entre les pêcheurs et les étrangers, ensuite entre les deux villes, qui prirent parti dans la querelle et étaient près d’en venir aux mains, lorsque la ''Pythie'' consultée leur ordonna de porter ce trépied au plus sage. On l’envoya d’abord à '''Thalès''', et ceux de ''Cos'' cédèrent sans peine à un seul particulier ce qu’ils allaient disputer par les armes à tous les ''Milésiens'' ensemble. '''Thalès''' le renvoya à '''Bias''', qui, disait-il, était plus sage que lui ; '''Bias''', avec la même modestie, le fit passer à un autre ; et après avoir été envoyé successivement à tous les sept, il revint une seconde fois à '''Thalès''' : enfin il fut porté à ''Thèbes'', et consacré à ''Apollon Isménien''. '''Théophraste''' dit qu’on l’envoya d’abord à '''Bias''', qui demeurait à ''Priène'' ; que '''Bias''' le fit porter à '''Thalès''' ; qu’après avoir été envoyé alternativement à tous les sages, il revint à '''Bias''', et qu’enfin il fut porté à ''Delphes''. Telle est la tradition la plus commune sur ce fait ; seulement quelques auteurs disent que ce n’était pas un trépied, mais un vase que '''Crésus''' envoyait à ''Delphes''; suivant d’autres, c’était une coupe que '''Bathyclès''' avait laissée.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f22.item ''chap. II''], traduction correcte et précédée du texte grec, par une société de professeurs et d’helléniste, 1862<br />(également disponible une édition de 1844 [https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm#1a ici] et de 1829 [[s:Les_Vies_des_hommes_illustres/Vie_de_Solon|là]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">Or ils (les sept sages) sont dits s’être trouvés aussi ensemble les uns avec les autres et à ''Delphes'' et de nouveau à ''Corinthe'', '''Périandre''' ayant préparé une certaine réunion commune d’eux et un banquet. Or le tour du trépied, et sa circulation à travers tous et sa cession se faisant avec une bienveillance pleine-d’émulation mit eux encore davantage en considération et renommée. Car des ''habitants-de-Cos'', comme on dit, jetant le filet, et des hôtes venus de ''Milet'' ayant acheté le coup qui n’était pas encore apparent, un trépied d’-or apparut étant retiré, lequel on dit '''Hélène''' naviguant pour revenir de ''Troie'' avoir jeté là, s’étant souvenue d’un certain oracle ancien. Mais une contestation ayant eu lieu d’abord aux hôtes vis-à-vis des pécheurs au sujet du trépied, ensuite les villes ayant pris-sur-elles le différend qui alla jusqu'à une guerre, la ''Pythie'' répondit aux-uns-et-aux-autres de donner le trépied au plus sage des hommes. Et d’abord il fut envoyé à '''Thalès''' à ''Milet'', les ''habitants-de-Cos'' donnant volontairement à celui-là seul le trépied, au sujet duquel ils avaient fait-la-guerre contre tous les ''Milésiens'' à-la-fois. Mais '''Thalès''' déclarant '''Bias''' plus sage que lui-même, il renvoya vers celui-là. Et de nouveau il fut envoyé par celui-là vers un autre, comme plus sage. Ensuite faisant-le-tour et étant envoyé-successivement ainsi il arriva pour la seconde fois à '''Thalès''' ; et à la fin transporté de ''Milet'' à ''Thèbes'', il fut consacré à ''Apollon Isménien''. Mais '''Théophraste''' dit le trépied avoir été envoyé d’abord à la vérité à ''Priène'' à '''Bias''', mais en-second-lieu à ''Milet'' à '''Thalès''', '''Bias''' l’ayant renvoyé ; et ainsi à travers tous (de l’un à l’autre) être venu-en-faisant-le-tour de nouveau à '''Bias''', et à la fin avoir été envoyé à ''Delphes''. Ces choses donc ont été répandues par plusieurs, excepté qu’ils disent le présent au lieu du trépied les uns être un vase à boire envoyé par '''Crésus''', les autres une coupe. '''Bathyclès''' rayant laissée (laissée par '''Bathyclès'''). </div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f23.item ''chap. II''], traduction littérale et juxtalinéaire présentant le mot à mot français en regard des mots grecs correspondants, par une société de professeurs et d’helléniste, 1862</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Chapitre VI.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;"> Récit d’une entrevue de '''Thalès''' avec '''Solon''', de son stoïcisme, de son célibat et de l’adoption du fils de sa sœur, '''Cybistus'''.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;"> '''VI.''' Ἰδίᾳ δ' Ἀναχάρσεώς τε πρὸς Σόλωνα καὶ πάλιν Θάλεω συνουσίαν τινὰ καὶ λόγους ἀναγράφουσι τοιούτους. [...]<br /><p style="text-indent: 15px"> '''VII.''' Πρὸς Θαλῆν δ' εἰς Μίλητον ἐλθόντα τὸν Σόλωνα θαυμάζειν ὅτι γάμου καὶ παιδοποιΐας τὸ παράπαν ἠμέληκε. Καὶ τὸν Θαλῆν τότε μὲν σιωπῆσαι, διαλιπόντα δ' ὀλίγας ἡμέρας ἄνδρα παρασκευάσαι ξένον, ἀρτίως ἥκειν φάσκοντα δεκαταῖον ἐξ Ἀθηνῶν. Πυθομένου δὲ τοῦ Σόλωνος εἰ δή τι καινὸν ἐν ταῖς Ἀθήναις, δεδιδαγμένον ἃ χρὴ λέγειν τὸν ἄνθρωπον, « οὐδέν,» εἰπεῖν, « ἕτερον, εἰ μὴ νὴ Δία νεανίσκου τινὸς ἦν ἐκφορὰ καὶ προὔπεμπεν ἡ πόλις. Ἦν γὰρ υἱός, ὡς ἔφασαν, ἀνδρὸς ἐνδόξου καὶ πρωτεύοντος ἀρετῇ τῶν πολιτῶν· οὐ παρῆν δέ, ἀλλ' ἀποδημεῖν ἔφασαν αὐτὸν ἤδη πολὺν χρόνον.» « Ὡς δυστυχὴς ἐκεῖνος,» φάναι τὸν Σόλωνα. « Τίνα δὲ ὠνόμαζον αὐτόν;» « ἤκουσα,» φάναι, « τοὔνομα,» τὸν ἄνθρωπον, « ἀλλ' οὐ μνημονεύω· πλὴν ὅτι πολὺς λόγος ἦν αὐτοῦ σοφίας καὶ δικαιοσύνης.» Οὕτω δὴ καθ' ἑκάστην ἀπόκρισιν τῷ φόβῳ προσαγόμενον τὸν Σόλωνα καὶ τέλος ἤδη συντεταραγμένον αὐτὸν ὑποβάλλειν τοὔνομα τῷ ξένῳ, πυνθανόμενον μὴ Σόλωνος ὁ τεθνηκὼς υἱὸς ὠνομάζετο. Φήσαντος δὲ τοῦ ἀνθρώπου, τὸν μὲν ὁρμῆσαι παίειν τὴν κεφαλὴν καὶ τἆλλα ποιεῖν καὶ λέγειν ἃ συμβαίνει τοῖς περιπαθοῦσι, τὸν δὲ Θαλῆν ἐπιλαβόμενον αὐτοῦ καὶ γελάσαντα, « ταῦτά τοι,» φάναι, « ὦ Σόλων, ἐμὲ γάμου καὶ παιδοποιΐας ἀφίστησιν, ἃ καὶ σὲ κατερείπει τὸν ἐρρωμενέστατον. Ἀλλὰ θάρρει τῶν λόγων ἕνεκα τούτων· οὐ γάρ εἰσιν ἀληθεῖς.» ταῦτα μὲν οὖν Ἕρμιππος ἱστορεῖν φησι Πάταικον, ὃς ἔφασκε τὴν Αἰσώπου ψυχὴν ἔχειν.<br /><p style="text-indent: 15px">'''VIII.''' Ἄτοπος δὲ καὶ ἀγεννὴς ὁ τῷ φόβῳ τῆς ἀποβολῆς τὴν κτῆσιν ὧν χρὴ προϊέμενος· οὕτω γὰρ ἄν τις οὐ πλοῦτον, οὐ δόξαν, οὐ σοφίαν ἀγαπήσειε παραγενομένην, δεδιὼς στέρεσθαι. Καὶ γὰρ ἀρετήν, ἧς κτῆμα μεῖζον οὐδὲν οὐδ' ἥδιον, ἐξισταμένην ὑπὸ νόσων καὶ φαρμάκων ὁρῶμεν· αὐτῷ τε '''Θαλῇ''' μὴ γήμαντι πλέον οὐδὲν εἰς ἀφοβίαν, εἰ μὴ καὶ φίλων κτῆσιν ἔφυγε καὶ οἰκείων καὶ πατρίδος. Ἀλλὰ καὶ παῖδα θετὸν ἔσχε ποιησάμενος αὐτὸς τὸν τῆς ἀδελφῆς, ὥς φασι, '''Κύβισθον'''.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm <u>Plutarque, Vie de Solon</u>], ''chap. II'', ''§§6-8'', traduction par [[w:Dominique_Ricard|Dominique Ricard]], 1844<br />(également disponible une édition de 1853 [https://remacle.org/bloodwolf/historiens/Plutarque/solonpierrron.htm ici] et de 1862 [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f26.item là])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 390px; border: 2px solid; text-indent: 15px"><br /><div style="margin: 0 2em; text-align: justify; direction: ltr;">'''VI.''' '''Solon''' connut [[w:Anacharsis|'''Anacharsis''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Anacharsis_back|<sup>🔄</sup>]] et '''Thalès''', et l’on cite des mots qui s’étaient dits dans leurs entrevues. <br /><p style="text-align: center"> [...]<br /><p style="text-indent: 15px; text-align: justify;">'''Solon''' alla à ''Milet'', pour voir '''Thalès''' : là, il lui témoigna sa surprise dans ce qu’il n’avait jamais voulu se marier et avoir des enfants. '''Thalès''' ne répondit rien sur l’heure; mais, quelques jours après, il fit paraître un étranger, qui disait arriver d’''Athènes'', et qu’il n’en était parti que depuis dix jours. '''Solon''' demanda à cet homme s’il n’y avait rien de nouveau à ''Athènes''. Celui-ci, à qui '''Thalès''' avait fait la leçon, répondit qu’il n’y avait rien de nouveau , sinon la mort d’un jeune homme dont toute la ville menait les funérailles. C’était, en effet, à ce qu’on disait, le fils d’un personnage considérable, d’une vertu éprouvée : le père n’était pas alors à ''Athènes'', et il voyageait depuis longtemps. « L’infortuné père! s’écria '''Solon'''. Mais comment s’appelait-il? — Je l’ai entendu nommer, répondit l’étranger, mais j’ai oublié son nom ; je me souviens seulement qu’on ne parlait que de sa sagesse et de sa justice. » A chacune de ces réponses, '''Solon''' sentait augmenter ses craintes; enfin, ne se possédant plus , il suggéra le nom à l’étranger, et lui demanda si le mort n’était pas le fils de '''Solon'''. « Oui. » répondit l’étranger. A cette parole, '''Solon''' se frappa la tête, et il se mit à faire et à dire tout ce qu’inspire une douleur violente. Alors '''Thalès''' lui prit la main, et lui dit en riant : « Voilà, '''Solon''', ce qui m’éloigne de me marier et d’avoir des enfants. J’ai redouté le coup sous lequel tu fléchis, toi le plus ferme des hommes. Mais rassure-toi; car il n’y a rien de vrai dans tout ce qu’on vient de te dire. » [[w:Hermippe_de_Smyrne|'''Hermippus''']] [[#Hermippe|<span id="Hermippe_back"><sup>'''I'''</sup></span>]] rapporte cette histoire d’après '''Patécus''', celui qui prétendait avoir hérité de l’âme d’[[w:Ésope|'''Ésope''']] [[#Ésope|<span id="Ésope_back"><sup>'''II'''</sup></span>]].<br /><p style="text-indent: 15px">Pourtant il y a faute de sens et de cœur à refuser d’acquérir les choses nécessaires, par la crainte de les perdre. A ce compte, on devra n’aimer ni la richesse, ni la gloire, ni la sagesse, quand on les possède, de peur d’en être privé. En effet, la vertu, le plus grand des biens et le plus doux, nous quitte quelquefois par l’action de certaines maladies ou de certains breuvages. '''Thalès''' lui-même, en ne se mariant point, n’était pas pour cela à l’abri de la crainte, à moins qu’il n'eût renoncé aussi à ses parents, à ses amis, à sa patrie. Mais il n’en était rien : il avait adopté, dit-on, '''Cybisthus''', le fils de sa sœur. </div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/viesdeshommesill01plut/page/n6/mode/1up?view=theater <u>Vie des Hommes Illustres de Plutarque</u>], [https://archive.org/details/viesdeshommesill01plut/page/182/mode/1up?view=theater ''Solon''], ''Chap. II'', ''§6'', [https://archive.org/details/viesdeshommesill01plut/page/185/mode/1up?view=theater p.185], traduit par [[w:Alexis_Pierron|Alexis Pierron]], professeur au lycée Louis-le-Grand, 1877<br />(également disponible une édition de 1853 [[s:Vies_des_hommes_illustres/Solon|ici]])</div> {{Boîte déroulante début|titre=NdA trad. par Alexis Pierron de 1877|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Hermippe_back|<span id="Hermippe"><sup>I</sup></span>]] Du nom propre [[w:Nom_théophore|''théophore'']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#théophore_back|<sup>🔄</sup>]] grec ancien Ἕρμιππος / Hérmippos [[wikt:en:Ἕρμιππος#Ancient_Greek|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ du nom propre Ἑρμῆς / Hermês [[wikt:en:Ἑρμῆς#Ancient_Greek|(en)]], « Hermès » ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du nom commun ἵππος / híppos [[wikt:en:ἵππος#Ancient_Greek|(en)]], « cheval » ;<br /><p style="margin: 0 2em; text-indent: 15px">« Péripatéticien » grec, disciple de [[w:Callimaque_de_Cyr%C3%A8ne|Callimaque de ''Cyrène'']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Callimaque_back|<sup>🔄</sup>]], auteur de nombreuses biographies toutes perdues, mais listées par plusieurs auteurs.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] seconde moitié du [[w:IIIe_siècle_av._J.-C.|III<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]]) [https://books.google.fr/books?id=DrvWAAAAMAAJ&newbks=1&newbks_redir=0&lpg=PA497&dq=bibliogroup%3A%22Dictionnaire%20des%20philosophes%20antiques%22&hl=fr&pg=PA655#v=onepage&q&f=false {{Info|<sup>🔍</sup>|Dictionnaire des Philosophes Antiques, publié sous la direction de Richard Goulet, Volume III, §86 - Hermippe de Smyrne}}] <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Ésope_back|<span id="Ésope"><sup>II</sup></span>]] Du nom propre grec ancien Αἴσωπος / Aísōpos [[wikt:en:Αἴσωπος#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">➥ du nom commun αἶσα / aîsa, « destin »;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du nom commun ὄψ / óps, « voix »;<br /><br /><p style="margin: 0 2em; text-indent: 15px">Auteur grec de fable.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] seconde moitié du [[w:VIe_siècle_av._J.-C.|VI<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]]) [https://books.google.fr/books?id=DrvWAAAAMAAJ&newbks=1&newbks_redir=0&lpg=PA497&dq=bibliogroup%3A%22Dictionnaire%20des%20philosophes%20antiques%22&hl=fr&pg=PA240#v=onepage&q&f=false {{Info|<sup>🔍</sup>|Dictionnaire des Philosophes Antiques, publié sous la direction de Richard Goulet, Volume III, §60 - Ésope(Αἴσωπος)}}]'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">'''V.''' Voici les particularités qu’on raconte d’une entrevue de '''Solon''' avec '''Anacharsis''', et d’un entretien qu’il eut avec '''Thalès'''.<br /><p style="text-align: center"> [...]<br /><p style="text-indent: 15px; text-align: justify;">'''VI.''' '''Solon''', étant allé à ''Milet'' pour voir '''Thalès''', lui témoigna sa surprise de ce qu’il n’avait jamais voulu se marier et avoir des enfants. '''Thalès''' ne lui répondit rien dans le moment; mais ayant laissé passer quelques jours, il fit paraître un étranger qui disait arriver d’''Athènes'', d’où il était parti depuis dix jours. '''Solon''' lui demanda s’il n’y avait rien de nouveau, lorsqu’il en était parti. Cet homme, à qui '''Thalès''' avait fait la leçon, lui répondit qu’il n’y avait autre chose que la mort d’un jeune homme dont toute la ville accompagnait le convoi. C’était, disait-on, le fils d’un des premiers et des plus vertueux citoyens, qui n'’était pas alors à ''Athènes'' et qui voyageait depuis longtemps, « Le malheureux père! s’écria Solon. Comment s’appelle-t-il? ― Je l’ai entendu nommer, répondit l’étranger; mais j’ai oublié son nom; je me souviens seulement qu’on ne parlait que de sa sagesse et de sa justice. » A chacune de ces réponses, les craintes de '''Solon''' augmentaient; enfin, troublé, hors de lui-même, il suggéra le nom à l’étranger, et lui demanda si ce jeune homme n’était pas le fils de '''Solon'''. « C’est lui-même, » dit l’autre. A cette parole, '''Solon''', se frappant la tête, se mit à faire et à dire tout ce que la douleur la plus violente peut inspirer. Alors '''Thalès''' l’arrêta et lui dit en souriant : « Voilà, '''Solon''', ce qui m’a éloigné de me marier et d’avoir des enfants; j’ai redouté le coup qui vous accable aujourd’hui, et contre lequel toute votre fermeté est impuissante. Mais rassurez-vous ; il n’y a rien de vrai dans tout ce qu’on vient de vous dire. » '''Hermippus''' rapporte cette histoire d’après le récit qu’en fait '''Patécus''', qui prétendait avoir hérité de l’âme d’'''Ésope'''.<br /><p style="text-indent: 15px; text-align: justify;">'''VII.''' Cependant c’est manquer de sens et de courage que de renoncer à acquérir des choses nécessaires par la crainte de les perdre. A ce compte, il ne faudrait aimer ni la richesse, ni la gloire, ni la sagesse, quand on les possède, de peur d’en être privé. La vertu même, le plus grand et le plus agréable des biens, se perd souvent par l’effet. de quelques maladies ou de certains breuvages. '''Thalès''' lui-même, en ne se mariant point, n’était pas à l’abri de toute crainte, à moins qu’il ne renonçât aussi à ses parents, à ses amis et à sa patrie. Mais au contraire, il avait adopté '''Cybistus''', le fils de sa sœur. </div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f26.item ''chap. II''], traduction correcte et précédée du texte grec, par une société de professeurs et d’helléniste, 1862<br />(également disponible une édition de 1844 [https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm#1a ici] et de 1829 [[s:Les_Vies_des_hommes_illustres/Vie_de_Solon|là]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">'''V.''' Mais en particulier on rapporte une certaine entrevue et de tels discours et d’'''Anacharsis''' à '''Solon''' et une-autre-fois de '''Thalès'''.<br /><p style="text-align: center"> [...]<br /><p style="text-indent: 15px; text-align: justify;">'''VI.''' Et on raconte '''Solon''' étant venu vers '''Thalès''' à ''Milet'' s’étonner de ce qu’il avait négligé absolument le mariage et la création-d’enfants. Et '''Thalès''' alors à la vérité s’être tu, mais ayant laissé-d’intervalle peu-de jours, avoir aposté un homme étranger, disant être arrivé récemment parti-depuis-dix-jours d’''Athènes''. Et '''Solon''' s’étant informé si donc il y a quelque chose de nouveau à ''Athènes'', l’homme instruit des choses qu’il faut dire n’avoir dit aucune autre chose, si ce n’est :<br /><p style="text-indent: 15px"> « Par '''Jupiter''', il y avait le convoi d’un certain jeune-homme, et la ville l’accompagnait. Car il était fils, comme on disait, d’un homme illustre et étant-le-premier des citoyens par la vertu; mais il n’était-pas-présent, mais on disait lui être-en-voyage depuis un temps déjà long.<br /><p style="text-align: left; text-indent: 15px">― Combien cet homme-là est malheureux! avoir dit '''Solon'''. Mais quel (comment) appelaient-ils lui ?<br /><p style="text-align: left; text-indent: 15px">― J’ai entendu le nom, avoir dit l’homme (répondit l’étranger), mais je ne me le rappelle pas; excepté qu’un discours fréquent était de la sagesse et de la justice de lui. »<br /><p style="text-indent: 15px">'''Solon''' donc étant-approché ainsi de-la crainte à chaque réponse, et à la fin déjà étant tout-troublé, lui-même avoir suggéré le nom à l’étranger, demandant si le mort n’était pas nommé fils de '''Solon'''. Et l’homme ayant dit-oui, celui-ci ('''Solon''') avoir commencé à frapper sa tête, et à faire et à dire les autres choses, qu’il arrive de faire et de dire à ceux affligés-à-l’excès. Mais '''Thalès''' ayant arrêté lui, et ayant ri, avoir dit :<br /><p style="text-indent: 15px">« Ces choses donc, ô '''Solon''', écartent moi du mariage et de la création-d’enfants, lesquelles abattent même toi le très-fort. Mais aie-confiance quant-à ces discours: car ils ne sont pas vrais. »<br /><p style="text-indent: 15px">'''Hermippe''' donc dit '''Patécus''', qui disait-souvent avoir l’âme d’'''Ésope''', raconter ces choses. <br /><p style="text-indent: 15px">'''VII.''' Or il est absurde et dépourvu de courage celui rejetant par la crainte de la perte l’acquisition des choses qu’il faut; car ainsi quelqu’un n’aimerait pas la richesse, n’aimerait pas la gloire, n’aimerait pas la sagesse étant survenue à lui, craignant (par crainte) d’en être privé. Et en effet nous voyons la vertu, au prix de laquelle aucune possession n’est plus grande, ni plus agréable, déplacée (chassée) par des maladies et des breuvages; et rien de plus n’être pour l’exemption-de-crainte à '''Thalès''' lui-même ne s’étant pas marié, s’il n’avait pas évité la possession et d’amis et de parents et de patrie. Mais même il eut un fils adoptif se l’étant fait tel lui-même, celui de sa sœur, comme on dit '''Cybisthe'''.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f27.item ''chap. II''], traduction littérale et juxtalinéaire présentant le mot à mot français en regard des mots grecs correspondants, par une société de professeurs et d’helléniste, 1862</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ====== <div style="text-align: center;">Paragraphe XV.</div> ====== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;"> Testament d’inhumation de '''Thalès'''.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">'''XV.''' Καὶ φόβοι τινὲς ἐκ δεισιδαιμονίας ἅμα καὶ φάσματα κατεῖχε τὴν πόλιν, οἵ τε μάντεις ἄγη καὶ μιασμοὺς δεομένους καθαρμῶν προφαίνεσθαι διὰ τῶν ἱερῶν ἠγόρευον. Οὕτω δὴ μετάπεμπτος αὐτοῖς ἧκεν ἐκ Κρήτης Ἐπιμενίδης ὁ Φαίστιος, ὃν ἕβδομον ἐν τοῖς σοφοῖς καταριθμοῦσιν ἔνιοι τῶν οὐ προσιεμένων τὸν Περίανδρον. Ἐδόκει δέ τις εἶναι θεοφιλὴς καὶ σοφὸς περὶ τὰ θεῖα τὴν ἐνθουσιαστικὴν καὶ τελεστικὴν σοφίαν, διὸ καὶ παῖδα νύμφης ὄνομα Βάλτης καὶ Κούρητα νέον αὐτὸν οἱ τότε ἄνθρωποι προσηγόρευον. <br /><p style="text-align: center">[...]<br /><p style="text-indent: 15px; text-align: justify;">Τὸ δὲ μέγιστον, ἱλασμοῖς τισι καὶ καθαρμοῖς καὶ ἱδρύσεσι κατοργιάσας καὶ καθοσιώσας τὴν πόλιν ὑπήκοον τοῦ δικαίου καὶ μᾶλλον εὐπειθῆ πρὸς ὁμόνοιαν κατέστησε. Λέγεται δὲ τὴν Μουνυχίαν ἰδὼν καὶ καταμαθὼν πολὺν χρόνον, εἰπεῖν πρὸς τοὺς παρόντας ὡς τυφλόν ἐστι τοῦ μέλλοντος ἄνθρωπος· ἐκφαγεῖν γὰρ ἂν Ἀθηναίους τοῖς αὑτῶν ὀδοῦσιν, εἰ προῄδεσαν ὅσα τὴν πόλιν ἀνιάσει τὸ χωρίον· ὅμοιον δέ τι καὶ Θαλῆν εἰκάσαι λέγουσι· κελεῦσαι γὰρ αὐτὸν ἔν τινι τόπῳ τῆς Μιλησίας φαύλῳ καὶ παρορωμένῳ τελευτήσαντα θεῖναι, προειπὼν ὡς ἀγορά ποτε τοῦτο Μιλησίων ἔσται τὸ χωρίον. Ἐπιμενίδης μὲν οὖν μάλιστα θαυμασθείς, καὶ χρήματα διδόντων πολλὰ καὶ τιμὰς μεγάλας τῶν Ἀθηναίων, οὐδὲν ἢ θαλλὸν ἀπὸ τῆς ἱερᾶς ἐλαίας αἰτησάμενος καὶ λαβὼν ἀπῆλθεν. </div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm <u>Plutarque, Vie de Solon</u>], ''chap. II'', ''§15'', traduction par [[w:Dominique_Ricard|Dominique Ricard]], 1844<br />(également disponible une édition de 1853 [https://remacle.org/bloodwolf/historiens/Plutarque/solonpierrron.htm ici] et de 1862 [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f26.item là])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 390px; border: 2px solid; text-indent: 15px"><br /><div style="margin: 0 2em; text-align: justify; direction: ltr;">'''XV.''' Au chagrin que ces pertes causèrent à ceux-ci [les ''Athéniens''], se joignirent des craintes superstitieuses dont la ville [''Athènes''] fut frappée, et qui venaient d’apparitions de spectres et de fantômes. Les devins déclarèrent aussi que l’état des victimes qu’ils avaient offertes annonçait des crimes et des profanations qu’il fallait expier. On fit donc venir de ''Crète'' [[w:Épiménide|'''Épiménide''']] [[#Épiménide|<span id="Épiménide_back"><sup>'''I'''</sup></span>]] le [[w:Phaistos|''Phestien'']] [[#Phaistos|<span id="Phaistos_back"><sup>'''II'''</sup></span>]], qui est mis au nombre des sept sages par ceux qui n’y comptent pas '''Périandre'''. Il passait pour un homme chéri des dieux, doué d’une grande sagesse, fort instruit des choses divines, surtout versé dans la science des inspirations et dans la connaissance des mystères; on l’appelait, même de son vivant, le nouveau [[w:Curètes|'''Curète''']], le fils de la nymphe '''Balté'''.<br /><p style="text-align: center">[...]<br /><p style="text-indent: 15px; text-align: justify;">Mais ce qui était plus important, il fit un grand nombre d’expiations et de sacrifices, il fonda plusieurs temples; et par ces différentes cérémonies il purifia entièrement la ville, en bannit l’impiété et l’injustice, et la rendit plus soumise, plus disposée à l’union et à la paix. On rapporte aussi que lorsqu’il vit le fort de [[w:Munichie|''Munychium'']] [[#Munichie|<span id="Munichie_back"><sup>'''III'''</sup></span>]], il le considéra longtemps, et dit à ceux qui l’accompagnaient : Que les hommes sont aveugles sur l’avenir ! Si les ''Athéniens'' pouvaient prévoir tous les maux que ce lieu doit un jour causer à leur ville, ils l’emporteraient à belles dents ». '''Thalès''' eut aussi, dit-on, un pressentiment à peu près semblable. Il ordonna qu’on l’enterrât dans le lieu le plus sauvage et le plus désert du territoire de ''Milet''; et il prédit aux ''Milésiens'' qu’un jour leur marché public y serait transporté. Les ''Athéniens'', pleins de reconnaissance et d’admiration pour '''Épiménide''', voulurent le combler d’honneurs et de présents; mais il ne demanda qu’une branche de l’olivier sacré, qui lui fut accordée, et il s’en retourna en ''Crète''.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://archive.org/details/viesdeshommesill01plut/page/n6/mode/1up?view=theater <u>Vie des Hommes Illustres de Plutarque</u>], [https://archive.org/details/viesdeshommesill01plut/page/182/mode/1up?view=theater ''Solon''], ''Chap. II'', ''§15'',[https://archive.org/details/viesdeshommesill01plut/page/185/mode/1up?view=theater p.185], traduit par [[w:Alexis_Pierron|Alexis Pierron]], professeur au lycée Louis-le-Grand, 1877<br />(également disponible une édition de 1853 [[s:Vies_des_hommes_illustres/Solon|ici]])</div> {{Boîte déroulante début|titre=NdA trad. par Alexis Pierron de 1877|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Épiménide_back|<span id="Épiménide"><sup>I</sup></span>]] Du nom propre grec ancien Ἐπιμενίδης / Epimenídēs [[wikt:en:Ἐπιμενίδης#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">Poète, philosophe et [[w:Iatromante|''iatromante'']] crétois.<br /><p style="text-align: right; margin: 0 2em;">([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l’on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] milieu du [[w:VIe_siècle_av._J.-C.|VI<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]]) <br /><br /><p style="margin: 0 2em; text-indent: 15px">'''[[#Phaistos_back|<span id="Phaistos"><sup>II</sup></span>]] Du nom propre grec ancien Φαιστός / Phaistós [[wikt:en:Φαιστός#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px">Ancienne ville du Sud de la [[w:Histoire_de_la_Crète#Antiquité|''Crète'']].<br /><br /><p style="margin: 0 2em; text-indent: 15px">[[#Munichie_back|<span id="Munichie"><sup>III</sup></span>]] Du nom propre grec ancien Μουνιχία / Mounikhia [[wikt:en:Μουνυχία#Grec_ancien|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px">Nom d’une colline du [[w:Le_Pirée|''Pirée'']] et de l’[[w:Port_de_Munichie|un des ports du ''Pirée'']].'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">'''XII.''' Au chagrin que ces pertes causèrent à ceux-ci, se joignirent des craintes superstitieuses dont la ville fut frappée, et qui venaient d’apparitions de spectres et de fantômes. Les devins déclarèrent aussi que l’état des victimes annonçait des crimes et des profanations qu’il fallait expier. On fit donc venir de ''Crète'' '''Épiménide''' le ''Phestien'' qui est mis au nombre des sept sages par ceux qui n’y comptent pas '''Périandre'''. Il passait pour un homme chéri des dieux, doué d’une grande sagesse, fort instruit des choses divines, surtout versé dans la science des inspirations et dans la connaissance des mystères; on l’appelait, même de son vivant, le nouveau '''Curète''', le fils de la nymphe '''Balté'''.<br /><p style="text-align: center">[...]<br /><p style="text-indent: 15px; text-align: justify;">Mais ce qui était plus important, il fit un grand nombre d’expiations et de sacrifices; il fonda plusieurs temples; et par ces différentes cérémonies, il purifia entièrement la ville, en bannit l’impiété et l’injustice, et la rendit plus soumise, plus disposée à l’union et à la paix. On rapporte aussi que lorsqu’il vit ''Munychie'', il la considéra longtemps, et dit à ceux qui l’accompagnaient :<br /><p style="text-align: justify; text-indent: 15px"> « Que les hommes sont aveugles sur l’avenir! Si les ''Athéniens'' pouvaient prévoir tous les maux que ce lieu doit un jour causer à leur ville, il l’emporteraient à belles dents. »<br /><p style="text-align: justify; text-indent: 15px"> '''Thalès''' eut aussi, dit-on, un pressentiment à peu près semblable. Il ordonna qu’on l’enterrât dans le lieu le plus sauvage et le plus désert du territoire de ''Milet''; et il prédit aux ''Milésiens'' qu’un jour leur marché public y serait transporté. Les ''Athéniens'', pleins de reconnaissance et d’admiration pour '''Épiménide''', voulurent le combler d’honneurs et de présents; mais il ne demanda qu’une branche de l’olivier sacré, qui lui fut accordée, et il s’en retourna en ''Crète''.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f58.item ''chap. II''], traduction correcte et précédée du texte grec, par une société de professeurs et d’helléniste, 1862<br />(également disponible une édition de 1844 [https://remacle.org/bloodwolf/historiens/Plutarque/solon.htm#1a ici] et de 1829 [[s:Les_Vies_des_hommes_illustres/Vie_de_Solon|là]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">Et certaines craintes nées de la superstition en-même-temps aussi des apparitions occupaient la ville; et les devins disaient des impiétés et des souillures ayant-besoin de purifications être indiquées par les victimes. Et ainsi vint à eux mandé (ils firent venir) de ''Crète'' '''Épiménide''' le ''Phestien'', que quelques-uns de ceux n’admettant pas '''Périandre''' comptent le septième parmi les sages. Or il avait-la-réputation d’être un homme ami-des-dieux, et habile dans la science de-l’inspiration et des-mystères. C’est-pourquoi les hommes d’alors appelaient lui et fils de la nymphe de nom (nommée) '''Balté''', et nouveau '''Curète'''.<br /><p style="text-align: center">[...]<br /><p style="text-indent: 15px; text-align: justify;">Mais le plus grand (le plus important), ayant initié-aux-mystères et ayant purifié la ville par certaines expiations et purifications et fondations, il la rendit prêtant-l’oreille à la justice et plus obéissante pour la concorde. Et il est dit, ayant vu ''Munychie'' et l’ayant examinée un temps long, avoir dit à ceux présents, que l’homme est un être aveugle sur l’avenir ; car les ''Athéniens'' avoir dû manger ''Munychie'' avec les dents d’eux-mêmes, s’ils avaient prévu en combien de choses cette place affligera la ville. Et on dit aussi '''Thalès''' avoir conjecturé quelque chose de semblable; lui avoir ordonné en effet de placer lui ayant cessé de vivre dans un certain lieu de la ''Milésie'' méprisé et dédaigné, ayant prédit que cet endroit sera (serait) un jour le marché des ''Milésiens''. '''Épiménide''' donc ayant été admiré très-grandement, et les ''Athéniens'' lui donnant des sommes nombreuses et des honneurs grands, n’ayant demandé rien qu’un rameau détaché de l’olivier sacré et l’ayant pris (reçu) s’en alla.</div> <div style="text-align: right; margin: 0 2em 0 1em;">[https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f7.item <u>Plutarque, Vie de Solon</u>], [https://gallica.bnf.fr/ark:/12148/bpt6k6226675w/f59.item ''chap. II''], traduction littérale et juxtalinéaire présentant le mot à mot français en regard des mots grecs correspondants, par une société de professeurs et d’helléniste, 1862</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">═✳═</div> === [[w:Œuvres_morales|Œuvres morales]] === <div style="text-align: center; margin: 0 1em;">Ensemble de 78 textes de traitant de sujets extrêmement variés (religieux, éthiques, politiques, philosophiques, littéraires, historiques), et s'inscrivant dans des genres littéraires également divers (traité, différentes sortes de dialogues, lettres, réponses à des Questions ([[wikt:ζήτημα#Grec_ancien|''zetemata'']]), « dits » ([[w:Apophtegme|''apophtegmes'']])).</div> ==== Le Banquet des Sept Sages ==== <div style="text-align: center; margin: 0 1em;">Dialogue faisant intervenir 20 personnages, dont une liste des sept sages : '''Thalès''', [[w:Solon|'''Solon''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Solon_back|<sup>🔄</sup>]], [[w:Bias_de_Priène|'''Bias''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Bias_back|<sup>🔄</sup>]], [[w:Chilon|'''Chilon''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Chilon_back|<sup>🔄</sup>]], [[w:Cléobule|'''Cléobule''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Cléobule_back|<sup>🔄</sup>]], [[w:Pittacos_de_Mytil%C3%A8ne|'''Pittacos''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Pittacos_back|<sup>🔄</sup>]], [[w:Périandre|'''Périandre''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Périandre_back|<sup>🔄</sup>]].<br />À ceux-ci se rajoutent : [[w:Anacharsis|'''Anacharsis''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Anacharsis_back|<sup>🔄</sup>]], '''Ésope''', '''Dioclès''', '''Nicarque''', [[w:Cléobuline|'''Cléobuline''']] [[#Cléobuline|<span id="Cléobuline_back"><sup>'''I'''</sup></span>]], '''Mélissa''', '''Niloxène''', '''Alexidème''', '''Ardalus''', '''Cléodème''', '''Mnésiphile''', '''Chersias''', [[w:Gorgias|'''Gorgias''']] [[Philosophie/Thalès_de_Milet/Textes_et_traductions_Ier_millénaire_AEC#Gorgias_back|<sup>🔄</sup>]].</div> {{Boîte déroulante début|titre=NdA Le Banquet des Septs Sages|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Cléobuline_back|<span id="Cléobuline"><sup>I</sup></span>]] Du nom propre grec ancien Κλεοβουλίνη / Kleoboulinè ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ de Κλεόβουλος / Kleóboulos [[wikt:en:Κλεόβουλος#Ancient_Greek|(en)]], « Cléobule, le père de Cléobuline » <br /><p style="margin: 0 2em; text-indent: 15px">➥ du suffixe adjectival féminin -ῑ́νη / -ī́nē [[wikt:en:-ίνη#Ancient_Greek|(en)]], relatif à la matière, au temps, etc. : « fait de, pendant la durée de » ;<br /><p style="margin: 0 2em; text-indent: 15px">Philosophe et poétesse grecque, célèbre pour ses énigmes, fille du [[w:Tyran|''tyran'']] Cléobule, qui la surnommerait Εὔμητις / Eúmētis, « la Prudente » selon Plutarque. '''<br/><br/></div> {{Boîte déroulante fin}} ===== <div style="text-align: center;">Paragraphe I.</div> ===== :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§1. Ἦ που προϊὼν ὁ χρόνος, ὦ '''Νίκαρχε''', πολὺ σκότος ἐπάξει τοῖς πράγμασι καὶ πᾶσαν ἀσάφειαν, εἰ νῦν ἐπὶ προσφάτοις οὕτω καὶ νεαροῖς λόγοι ψευδεῖς συντεθέντες ἔχουσι πίστιν. οὔτε γὰρ μόνων, ὡς ὑμεῖς ἀκηκόατε, τῶν ἑπτὰ γέγονε τὸ συμπόσιον, ἀλλὰ πλειόνων ἢ δὶς τοσούτων (ἐν οἷς καὶ αὐτὸς ἤμην, συνήθης μὲν ὢν '''Περιάνδρῳ''' διὰ τὴν τέχνην, ξένος δὲ '''Θάλεω'''· παρ´ ἐμοὶ γὰρ κατέλυσεν ὁ ἀνὴρ '''Περιάνδρου''' κελεύσαντος), οὔτε τοὺς λόγους ὀρθῶς ἀπεμνημόνευσεν ὅστις ἦν ὑμῖν ὁ διηγούμενος· ἦν δ´ ὡς ἔοικεν οὐδεὶς τῶν παραγεγονότων. ἀλλ´ ἐπεὶ σχολή τε πάρεστι πολλὴ καὶ τὸ γῆρας οὐκ ἀξιόπιστον ἐγγυήσασθαι τὴν ἀναβολὴν τοῦ λόγου, προθυμουμένοις ὑμῖν ἀπ´ ἀρχῆς ἅπαντα διηγήσομαι. </div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §1'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§1. '''D<span style ="font-size:85%;">IOCLÈS</span>'''. Certainement le long cours des âges, mon cher '''Nicarque''', jettera sur les faits d’épaisses ténèbres et une complète incertitude, puisque dès aujourd’hui, à propos de choses si récentes et si nouvelles, des relations mensongères et controuvées obtiennent crédit. Car d’abord le banquet en question ne se composait pas seulement des sept sages, comme vous autres l’avez ouï dire. Les convives étaient plus du double de ce nombre. J’en faisais moi-même partie, comme familier de '''Périandre''' en raison de notre profession commune, et comme hôte de '''Thalès''' : ce dernier était en effet descendu chez moi sur la recommandation de '''Périandre'''. Ensuite, on ne vous en a pas rapporté fidèlement les entretiens lorsqu’on vous a fait ce récit. Il faut que celui de qui vous le tenez n’ait pas été un des convives. Mais puisque nous avons un ample loisir et que la vieillesse est un garant trop peu sûr pour nous autoriser à remettre cet entretien, je vais, suivant votre désir unanime, vous en raconter tous les détails à partir du commencement. </div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §1'', traduction par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870<br/>(également disponible [[s:Page:Plutarque_-_Œuvres_complètes_de_Plutarque_-_Œuvres_morales_et_œuvres_diverses,_tome_1,_1870.djvu/424|ici]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">'''D<span style ="font-size:85%;">IOCLÈS</span>'''. Quelle incertitude et quelle obscurité la succession des temps ne doit-elle pas répandre sur l’histoire, mon cher '''Nicarque''', puisque, dans des faits récents, et qui se sont passés presque sous nos yeux, le faux prend la place du vrai ? Ce banquet n’était pas, comme on vous l’a dit, composé seulement des sept sages ; les convives étaient plus du double de ce nombre. J’y assistai moi-même, et comme ami de '''Périandre''', avec qui ma profession ma lié depuis longtemps [[#Dioclès_NdT_DR|<span id="Dioclès_NdT_DR_back"><sup>1</sup></span>]], et comme hôte de '''Thalès''', à qui '''Périandre''' avait fait marquer son logement chez moi. Celui qui vous a fait le récit de ce qui s’y est passé n’en était sûrement pas, et vous a trompé sur presque tous les points. Mais, puisque nous en avons le loisir, et que notre âge avancé ne nous permet guère de différer, je vais vous satisfaire et vous en raconter tous les détails.</div> <table cellspacing=15 style="margin: 0 4em; font-size:85%;"> <tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Dioclès_NdT_DR_back|<span id="Dioclès_NdT_DR"><sup>1.</sup></span>]] Dioclès était devin.''' </td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe II.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de la simplicité de '''Thalès''', de sa mesure de la pyramide d’''Égypte'', et de son aversion des [[w:Tyran|''tyrans'']] et des rois.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§2. Παρεσκευάκει μὲν γὰρ οὐκ ἐν τῇ πόλει τὴν ὑποδοχὴν ὁ Περίανδρος, ἀλλ´ ἐν τῷ περὶ τὸ Λέχαιον ἑστιατορίῳ παρὰ τὸ τῆς Ἀφροδίτης ἱερόν, ἧς ἦν καὶ ἡ θυσία. μετὰ γὰρ τὸν ἔρωτα τῆς μητρὸς αὐτοῦ προεμένης τὸν βίον ἑκουσίως οὐ τεθυκὼς τῇ Ἀφροδίτῃ, τότε πρῶτον ἔκ τινων ἐνυπνίων τῆς Μελίσσης ὥρμησε τιμᾶν καὶ θεραπεύειν τὴν θεόν. Τῶν δὲ κεκλημένων ἑκάστῳ συνωρὶς ἱκανῶς κεκοσμημένη προσήχθη· καὶ γὰρ ὥρα θέρους ἦν, καὶ τὴν ὁδὸν ἅπασαν ὑπὸ πλήθους ἁμαξῶν καὶ ἀνθρώπων ἄχρι θαλάττης κονιορτὸς καὶ θόρυβος κατεῖχεν. ὁ μέντοι Θαλῆς τὸ ζεῦγος ἐπὶ ταῖς θύραις ἰδὼν καὶ μειδιάσας ἀφῆκεν. ἐβαδίζομεν οὖν ἐκτραπόμενοι διὰ τῶν χωρίων, καθ´ ἡσυχίαν, καὶ μεθ´ ἡμῶν τρίτος ὁ Ναυκρατίτης Νειλόξενος, ἀνὴρ ἐπιεικὴς καὶ τοῖς περὶ Σόλωνα καὶ Θαλῆν γεγονὼς ἐν Αἰγύπτῳ συνήθης. ἐτύγχανε δὲ πρὸς Βίαντα πάλιν ἀπεσταλμένος· ὧν δὲ χάριν οὐδ´ αὐτὸς ᾔδει, πλὴν ὑπενόει πρόβλημα δεύτερον αὐτῷ κομίζειν ἐν βιβλίῳ κατασεσημασμένον· εἴρητο γάρ, εἰ Βίας ἀπαγορεύσειεν, ἐπιδεῖξαι τοῖς σοφωτάτοις Ἑλλήνων τὸ βιβλίον. « Ἕρμαιον » ὁ Νειλόξενος ἔφη « μοι γέγονεν ἐνταῦθα λαβεῖν ἅπαντας ὑμᾶς, καὶ κομίζω τὸ βιβλίον ὡς ὁρᾷς ἐπὶ τὸ δεῖπνον. » ἅμα δ´ ἡμῖν ἐπεδείκνυε. Καὶ ὁ Θαλῆς γελάσας « εἴ τι κακόν, » εἶπεν, « αὖθις εἰς Πριήνην· διαλύσει γὰρ ὁ Βίας, ὡς διέλυσεν αὐτὸς τὸ πρῶτον. » « Τί δ´ ἦν, » ἔφην ἐγώ, « τὸ πρῶτον; » « Ἱερεῖον, » εἶπεν, « ἔπεμψεν αὐτῷ, κελεύσας τὸ πονηρότατον ἐξελόντα καὶ χρηστότατον ἀποπέμψαι κρέας. ὁ δ´ ἡμέτερος εὖ καὶ καλῶς τὴν γλῶτταν ἐξελὼν ἔπεμψεν· ὅθεν εὐδοκιμῶν δῆλός ἐστι καὶ θαυμαζόμενος. » « Οὐ διὰ ταῦτ´ » ἔφη « μόνον » ὁ Νειλόξενος, « ἀλλ´ οὐ φεύγει τὸ φίλος εἶναι καὶ λέγεσθαι βασιλέων καθάπερ ὑμεῖς, ἐπεὶ σοῦ γε καὶ τἄλλα θαυμάζει, καὶ τῆς [[wikt:en:πυραμίδος#Ancient_Greek|πυραμίδος]] τὴν μέτρησιν ὑπερφυῶς ἠγάπησεν, ὅτι πάσης ἄνευ πραγματείας καὶ μηδενὸς ὀργάνου δεηθεὶς ἀλλὰ τὴν βακτηρίαν στήσας ἐπὶ τῷ πέρατι τῆς σκιᾶς ἣν ἡ πυραμὶς ἐποίει, γενομένων τῇ ἐπαφῇ τῆς ἀκτῖνος δυεῖν τριγώνων, ἔδειξας ὃν ἡ σκιὰ πρὸς τὴν σκιὰν λόγον εἶχε τὴν πυραμίδα πρὸς τὴν βακτηρίαν ἔχουσαν. ἀλλ´, ὅπερ ἔφην, διεβλήθης μισοβασιλεὺς εἶναι, καί τινες ὑβριστικαί σου περὶ τυράννων ἀποφάσεις ἀνεφέροντο πρὸς αὐτόν, ὡς ἐρωτηθεὶς ὑπὸ Μολπαγόρου τοῦ Ἴωνος τί παραδοξότατον εἴης ἑωρακώς, ἀποκρίναιο ‘τύραννον γέροντα,’ καὶ πάλιν ἔν τινι πότῳ, περὶ τῶν θηρίων λόγου γενομένου, φαίης κάκιστον εἶναι τῶν μὲν ἀγρίων θηρίων τὸν τύραννον, τῶν δ´ ἡμέρων τὸν κόλακα· ταῦτα γάρ, εἰ καὶ πάνυ προσποιοῦνται διαφέρειν οἱ βασιλεῖς τῶν τυράννων, οὐκ εὐμενῶς ἀκούουσιν. » « Ἀλλὰ τοῦτο μέν, » εἶπεν ὁ Θαλῆς, « Πιττακοῦ ἐστιν, εἰρημένον ἐν παιδιᾷ ποτε πρὸς Μυρσίλον· ἐγὼ δὲ θαυμάσαιμ´ ἄν, » ἔφη, « οὐ τύραννον ἀλλὰ [[wikt:κυβερνήτης#Grec_ancien|'''κυβερνήτην''']] γέροντα θεασάμενος. πρὸς δὲ τὴν μετάθεσιν τὸ τοῦ νεανίσκου πέπονθα τοῦ βαλόντος μὲν ἐπὶ τὴν κύνα πατάξαντος δὲ τὴν μητρυιὰν καὶ εἰπόντος ‘οὐδ´ οὕτω κακῶς.’ διὸ καὶ Σόλωνα σοφώτατον ἡγησάμην οὐ δεξάμενον τυραννεῖν. καὶ Πιττακὸς οὗτος εἰ μοναρχίᾳ μὴ προσῆλθεν, οὐκ ἂν εἶπεν ὡς ‘χαλεπὸν ἐσθλὸν ἔμμεναι.’ Περίανδρος δ´ ἔοικεν ὥσπερ ἐν νοσήματι πατρῴῳ τῇ τυραννίδι κατειλημμένος οὐ φαύλως ἐξαναφέρειν, χρώμενος ὁμιλίαις ὑγιειναῖς ἄχρι γε νῦν καὶ συνουσίας ἀνδρῶν νοῦν ἐχόντων ἐπαγόμενος, ἃς δὲ '''Θρασύβουλος''' αὐτῷ κολούσεις τῶν ἄκρων οὑμὸς πολίτης ὑφηγεῖται μὴ προσιέμενος. γεωργοῦ γὰρ [[wikt:en:αἶρα#Ancient_Greek|αἴρας]] καὶ [[wikt:ononis|ὀνώνιδας]] ἀντὶ [[wikt:πυρός#Grec_ancien|πυρῶν]] καὶ [[wikt:κριθή#Grec_ancien|κριθῶν]] συγκομίζειν ἐθέλοντος οὐδὲν διαφέρει τύραννος ἀνδραπόδων μᾶλλον ἄρχειν ἢ ἀνδρῶν βουλόμενος· ἓν γὰρ ἀντὶ πολλῶν κακῶν ἀγαθὸν αἱ δυναστεῖαι τὴν τιμὴν ἔχουσι καὶ τὴν δόξαν, ἄνπερ ἀγαθῶν ὡς κρείττονες ἄρχωσι καὶ μεγάλων μείζονες εἶναι δοκῶσι· τὴν δ´ ἀσφάλειαν ἀγαπῶντας ἄνευ τοῦ καλοῦ προβάτων ἔδει πολλῶν καὶ ἵππων καὶ βοῶν ἄρχειν, μὴ ἀνθρώπων. ἀλλὰ γὰρ εἰς οὐδὲν προσήκοντας ἐμβέβληκεν ἡμᾶς, » ἔφη, « ὁ ξένος οὑτοσὶ λόγους, ἀμελήσας λέγειν τε καὶ ζητεῖν ἃ ἁρμόττει ἐπὶ δεῖπνον βαδίζουσιν. ἦ γὰρ οὐκ οἴει, καθάπερ ἑστιάσοντος ἔστι τις παρασκευή, καὶ δειπνήσοντος εἶναι; Συβαρῖται μὲν γὰρ ὡς ἔοικε πρὸ ἐνιαυτοῦ τὰς κλήσεις ποιοῦνται τῶν γυναικῶν, ὅπως ἐκγένοιτο κατὰ σχολὴν παρασκευασαμέναις ἐσθῆτι καὶ χρυσῷ φοιτᾶν ἐπὶ τὸ δεῖπνον· ἐγὼ δὲ πλείονος οἶμαι χρόνου δεῖσθαι τὴν ἀληθινὴν τοῦ δειπνήσοντος ὀρθῶς παρασκευήν, ὅσῳ χαλεπώτερόν ἐστιν ἤθει τὸν πρέποντα κόσμον ἢ σώματι τὸν περιττὸν ἐξευρεῖν καὶ ἄχρηστον. οὐ γὰρ ὡς ἀγγεῖον ἥκει κομίζων ἑαυτὸν ἐμπλῆσαι πρὸς τὸ δεῖπνον ὁ νοῦν ἔχων, ἀλλὰ καὶ σπουδάσαι τι καὶ παῖξαι καὶ ἀκοῦσαι καὶ εἰπεῖν ὡς ὁ καιρὸς παρακαλεῖ τοὺς συνόντας, εἰ μέλλουσι μετ´ ἀλλήλων ἡδέως ἔσεσθαι. καὶ γὰρ καὶ ὄψον πονηρὸν ἔστι παρώσασθαι, κἂν οἶνος ᾖ φαῦλος, ἐπὶ τὰς νύμφας καταφυγεῖν· σύνδειπνος δὲ κεφαλαλγὴς καὶ βαρὺς καὶ ἀνάγωγος παντὸς μὲν οἴνου καὶ ὄψου πάσης δὲ μουσουργοῦ χάριν ἀπόλλυσι καὶ λυμαίνεται, καὶ οὐδ´ ἀπεμέσαι τὴν τοιαύτην ἀηδίαν ἕτοιμόν ἐστιν, ἀλλ´ ἐνίοις εἰς ἅπαντα τὸν βίον ἐμμένει τὸ πρὸς ἀλλήλους δυσάρεστον, ὥσπερ ἑωλοκρασία τις ὕβρεως ἢ ὀργῆς ἐν οἴνῳ γενομένης. ὅθεν ἄριστα Χίλων, καλούμενος ἐχθές, οὐ πρότερον ὡμολόγησεν ἢ πυθέσθαι τῶν κεκλημένων ἕκαστον. ἔφη γὰρ ὅτι σύμπλουν ἀγνώμονα δεῖ φέρειν καὶ σύσκηνον οἷς πλεῖν ἀνάγκη καὶ στρατεύεσθαι· τὸ δὲ συμπόταις ἑαυτὸν ὡς ἔτυχε καταμιγνύειν οὐ νοῦν ἔχοντος ἀνδρός ἐστιν. ὁ δ´ Αἰγύπτιος σκελετός, ὃν ἐπιεικῶς εἰσφέροντες εἰς τὰ συμπόσια προτίθενται καὶ παρακαλοῦσι μεμνῆσθαι τάχα δὴ τοιούτους ἐσομένους, καίπερ ἄχαρις καὶ ἄωρος ἐπίκωμος ἥκων, ὅμως ἔχει τινὰ καιρόν, καὶ εἰ μὴ πρὸς τὸ πίνειν καὶ ἡδυπαθεῖν ἀλλὰ πρὸς φιλίαν καὶ ἀγάπησιν ἀλλήλων προτρέπεται, καὶ παρακαλεῖ τὸν βίον μὴ τῷ χρόνῳ βραχὺν ὄντα πράγμασι κακοῖς μακρὸν ποιεῖν. » </div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §2'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§2. La réception avait été préparée par '''Périandre''', non pas dans la ville même, mais dans son [[w:Cénacle|''cénacle'']] du port de [[w:Léchaion|''Léchée'']], près du temple de '''Vénus''' en l’honneur de laquelle il y avait même un sacrifice. Car depuis l’incestueux amour à la suite duquel sa mère avait volontairement abandonné la vie, '''Périandre''' n’avait pas sacrifié à '''Vénus''' ; et c’était alors la première fois que, d’après certains songes de '''Mélissa''' il s’était décidé à honorer la déesse et à lui rendre des hommages. Pour chacun des invités on avait amené un attelage approprié convenablement. Nous étions en été, et tout le long de la route, en raison du grand nombre de chariots et de piétons, ce n’était jusqu’à la mer que poussière et que bruit. Pourtant '''Thalès''', ayant vu l’attelage à notre porte, se mit à sourire et le renvoya. Nous partîmes donc à pied en faisant un détour, et à travers champs nous cheminâmes à loisir. Un troisième compagnon s’était joint à nous, '''Niloxène''' de ''Naucratie'', homme plein de mérite, que '''Thalès''' et '''Solon''' avaient autrefois beaucoup connu en ''Égypte''. Il se trouvait envoyé de nouveau vers '''Bias''' ; mais pour quelle mission ? Il n’en savait rien lui-même, hormis qu’il se soupçonnait porteur d’une seconde question à résoudre, contenue dans un pli cacheté; et il lui avait été dit, au cas où '''Bias''' renoncerait, de la présenter aux plus sages d’entre les Grecs. « C’est », dit '''Niloxène''', « une chance heureuse que de vous trouver ici tous, et j’apporte, comme vous voyez, cette lettre pour le banquet. » En même temps, il nous la faisait voir. '''Thalès''' se mit à rire : « Si c’est une question épineuse », dit-il, « qu’on aille encore à ''Priène'' : '''Bias''' la résoudra comme il a résolu la première. » — « Quelle était donc cette première question ? » lui demandai-je. — « Le roi d’Égypte », dit '''Thalès''', « avait envoyé à '''Bias''' une victime, en lui faisant dire d’en couper ce qu’il y avait de plus mauvais et de meilleur, et de le lui renvoyer. Notre sage, avec un discernement merveilleux, en ôta la langue et la fit porter au Roi. Voilà ce qui lui a valu une estime et une admiration si déclarée ». — « Cette raison n’est pas la seule », ajouta '''Niloxène''' ; « c’est encore, que '''Bias''' ne fuit pas, comme vous autres, l’amitié des rois eux-mêmes. Ainsi, vous, '''Thalès''', le roi d’Égypte vous admire beaucoup, et, entre autres choses, il a été, au-delà de ce qu’on peut dire, ravi de la manière dont vous avez mesuré la pyramide sans le moindre embarras et sans avoir eu besoin d’aucun instrument. Après avoir dressé votre bâton à l’extrémité de l’ombre que projetait la pyramide, vous construisîtes deux triangles par la tangence d’un rayon, et vous démontrâtes qu’il y avait la même proportion entre la hauteur du bâton et la hauteur de la pyramide qu’entre la longueur des deux ombres. Mais, comme j’ai dit, on vous accuse de détester les rois ; quelques boutades injurieuses prononcées par vous contre des ''tyrans'' ont été rapportées à [[w:Ahmôsis_II|'''Amasis''']]. Par exemple, l’Ionien '''Molpagore''' vous ayant demandé ce que vous aviez jamais vu de plus extraordinaire, vous lui auriez répondu : « C’est un tyran parvenu à la vieillesse. » Une autre fois, dans un festin, la conversation étant venue à tomber sur les bêtes féroces, vous auriez dit : « La plus méchante bête parmi les animaux sauvages, c’est le tyran, et parmi les animaux apprivoisés le flatteur. » De tels propos ne sont pas de ceux que les rois entendent avec plaisir, lors même qu’ils affectent de n’avoir rien de commun avec les tyrans. » Pour cette dernière réponse, dit '''Thalès''', elle est de '''Pittacus''' : il l’avait adressée un jour en plaisantant à '''Myrsile'''. Quant au premier propos, ce n’était pas « un tyran » que j’avais dit, mais « un [[wikt:κυβερνήτης#Grec_ancien|''pilote'']] », qui soit parvenu à la vieillesse. Toutefois, puisqu’on a changé la destination du mot, je fais comme le jeune homme qui, ayant jeté une pierre à un chien, atteignit sa belle-mère et s’écria : Même ainsi, ce n’est pas mal. » C’est pourquoi je regardai '''Solon''' comme éminemment sage lorsqu’il n’accepta pas la tyrannie ; et '''Pittacus''', s’il n’eût approché de la monarchie, n’aurait pas eu à dire : « Il est difficile d’être homme de bien. » Quant à '''Périandre''', il semble qu’ayant été saisi par le souverain pouvoir comme par une maladie de famille, il ne s’en tire pas trop mal. Il use, au moins jusqu’à présent, de sociétés salutaires. Il réunit, pour entretenir commerce avec eux, ses hommes remplis de sens ; et le conseil que lui a donné mon compatriote [[w:Thrasybule_de_Milet|'''Thrasybule''']] [[#Thrasybule|<span id="Thrasybule_back"><sup>'''I'''</sup></span>]], de décapiter l’aristocratie, il ne l’a pas accepté. Entre un laboureur qui aimerait mieux voir dans son champ de l’[[w:Ivraie|''ivraie'']] ou de l’[[w:Orobanche|''orobanche'']] que de l’orge ou du blé, et un tyran qui veut régner sur des esclaves plutôt que sur des hommes de coeur, je ne vois aucune différence. Un seul bien compense les maux nombreux attachés au pouvoir des tyrans : c’est la gloire et l’honneur qui leur sont réservés lorsque, commandant à des hommes vertueux, ils sont plus vertueux eux-mêmes, et qu’au milieu de grands ils se montrent plus grands. Ceux qui préfèrent leur sûreté en renonçant à ce beau rôle, étaient faits pour réunir sous leur main beaucoup de moutons, de chevaux et de boeufs, mais non des hommes. » « Du reste », continua '''Thalès''', « ce sont propos sans portée aucune que ceux où nous a jetés cet étranger, et nous avons omis de dire et de chercher les choses qui conviennent bien à des gens partis pour un banquet. Ne croyez-vous pas, '''Nicarque''', qu’il y ait des préparatifs à faire quand on vient prendre place à un festin, comme il y en a pour celui qui doit le donner ? Les [[w:Sybaris|''Sybarites'']] [[#Sybaris|<span id="Sybaris_back"><sup>'''II'''</sup></span>]], à ce qu’il paraît, s’y prennent un an d’avance pour adresser leurs invitations aux femmes, afin qu’elles puissent à loisir préparer leur toilette et leurs bijoux en or avant de se rendre au festin; et, selon moi, il faut plus de temps encore à un convive pour les préparatifs vraiment nécessaires, parce qu’il est plus difficile de trouver un ajustement convenable pour son moral que la vaine et inutile parure dont on s’inquiète pour son corps. Un homme sensé ne se transporte pas à un festin comme un bocal qu’il s’agit d’y remplir. Il songe à trouver là une occasion de passer tour à tour du sérieux au badinage, d’entendre et de tenir lui-même ces propos auxquels la circonstance invite les convives s’ils veulent se rendre la réunion agréable les uns aux autres. En effet on est libre de repousser un mauvais ragoût, et si le vin ne vaut rien on peut « recourir aux [[w:Naïades|''Naïades'']] »; mais un convive qui vous donne mal à la tête, qui est lourd, qui ne sait pas se conduire, vous fait perdre et vous gâte le plaisir de tout vin, de toute bonne chère, de toute musique. On n’est même pas le maître de se débarrasser aussi complétement qu’on le voudrait d’un tel désagrément. Quelques-uns en gardent tant qu’ils vivent du mécontentement les uns contre les autres : il leur semble qu’il leur reste comme un arrière-goût de viandes mal digérées, parce qu’ils conservent le souvenir d’injures ou de colères échangées dans le vin. C’est pour cela que '''Chilon''', invité hier, n’a pas voulu promettre avant d’avoir su le nom de chacun des convives : « Car on est bien obligé, a-t-il dit, de supporter un désagréable compagnon de traversée, de tente, quand il faut être sur terre ou à l’armée ; mais se mêler indifféremment à table avec les premiers venus, n’est pas le propre d’un homme sensé. » Le squelette des ''Égyptiens'', qu’ils ont la sage coutume de produire et de placer dans la salle du festin afin d’engager à se souvenir que l’on sera bientôt comme lui, survient là comme un convive assez désagréable et intempestif, mais enfin la présence s’en explique. Si cette vue n’excite pas à boire et à se réjouir, elle engage du moins à s’aimer, à se chérir les uns les autres, et elle exhorte à ne pas allonger par des tracas pénibles une existence dont la durée est si courte. </div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §2'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> {{Boîte déroulante début|titre=NdA trad. par Victor Bétolaud de 1870|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Thrasybule_back|<span id="Thrasybule"><sup>I</sup></span>]] Du nom propre grec ancien Θρᾰσῠ́βουλος / Thrăsŭ́boulos [[wikt:en:Θρασύβουλος#Ancient_Greek|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ de l’adjectif [[wikt:θρασύς#Grec_ancien|θρᾰσύς / thrăsús]], « confiant, audacieux » ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ du nom commun [[wikt:βουλή#Grec_ancien|βουλή / boulế]], « Volonté. Décision, conseil. Conseil, sénat athénien. »; du verbe [[wikt:βούλομαι#Grec_ancien|βούλομαι / boúlomai]], « Vouloir, désirer, souhaiter. Vouloir bien, consentir à. » ;<br /><p style="margin: 0 2em; text-indent: 15px">➥ +‎ -ος (-os).<br /><br /><p style="margin: 0 2em; text-indent: 15px">[[#Sybaris_back|<span id="Sybaris"><sup>II</sup></span>]] Du nom propre grec ancien Σῠ́βᾰρῐς / Sŭ́bărĭs [[wikt:en:Σύβαρις#Ancient_Greek|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px">[[w:Colonisation grecque|Colonie grecque]] du sud de l’Italie (en [[w:Calabre|Calabre]] actuelle), fondée au [[w:VIIIe_siècle_av._J.-C.|VIII<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]] dans le cadre du mouvement d’établissement et d’[[w:Colonisation_grecque|''essaimage'']] des Grecs vers l’Occident, particulièrement en [[w:Grande-Grèce|''Grande-Grèce'']]. Réputée dès l’Antiquité pour sa richesse devenue proverbiale, ainsi que pour son emprise sur les peuples voisins et différentes cités grecques de son voisinage, elle est détruite à l’issue d’une [[w:Guerre entre Sybaris et Crotone|guerre]] qui l’oppose à [[w:Crotone|''Crotone'']] à la fin du [[w:VIe_siècle_av._J.-C.|VI<sup>ème</sup> siècle {{Info|AEC|Avant l’Ère Commune}}]], et enfouie sous les eaux du fleuve ''Crathis'' (aujourd’hui [[Crati]]), avant de voir son site réoccupé, soixante ans plus tard, par la colonie [[w:Panhellénisme|''panhellénique'']] de [[w:Thourioï|''Thourioï'']].'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">'''Périandre''' avait fait préparer le banquet, non à la ville même, mais au ''port de Léchée'' [[#Léchée_NdT_DR|<span id="Léchée_NdT_DR_back"><sup>1</sup></span>]], dans une salle voisine du ''temple de Vénus'', à qui l’on devait offrir un sacrifice. Depuis que sa mère, victime d’un malheureux amour [[#Cratée_NdT_DR|<span id="Cratée_NdT_DR_back"><sup>2</sup></span>]], s’était donné la mort, il n’avait pas encore sacrifié à cette déesse, et c’était la première fois qu’il y pensait, d’après un songe de '''Mélisse''' [[#Mélisse_NdT_DR|<span id="Mélisse_NdT_DR_back"><sup>3</sup></span>]]. Nous étions dans les plus grandes chaleurs de l’été. Le grand nombre des voitures et des gens de pied qui fréquentaient le chemin qui conduit à la mer, l’avaient couvert de poussière et le rendaient fort incommode pour les voyageurs. On avait amené, pour chaque convive, un char commode et proprement orné. '''Thalès''', en voyant le sien à ma porte, se mit à sourire et le refusa. Nous primes donc, à travers les champs, un sentier détourné, et nous allâmes, en nous promenant, suivis de '''Niloxène''' de ''Naucrate'' [[#Niloxène_NdT_DR|<span id="Niloxène_NdT_DR_back"><sup>4</sup></span>]], homme du plus grand mérite, que '''Thalès''' et '''Solon''' avaient autrefois beaucoup connu en ''Égypte''. Il était envoyé, pour la seconde fois, vers '''Bias''', sans savoir lui-même quel était l’objet de sa mission. Il se doutait seulement que la lettre dont '''Amasis''' l’avait chargé contenait une seconde question à résoudre. Il avait ordre, en cas que '''Bias''' refusât d’y répondre, de la proposer aux plus sages d’entre les ''Grecs''. Dès qu’il m’eut aperçu, il me dit en me montrant sa lettre : « J’ai du bonheur aujourd’hui. Cette lettre vous trouve tous réunis. Je la porte au banquet, comme vous voyez. - Si c’est une question épineuse, dit '''Thalès''' en souriant, retournez à ''Priene'' [[#Priene_NdT_DR|<span id="Priene_NdT_DR_back"><sup>5</sup></span>]], '''Bias''' la résoudra, comme il a résolu la première. - Quelle était cette première question, demandai-je à '''Thalès''' ? - Le roi d’Égypte, me répondit-il, avait envoyé une victime à '''Bias''', en lui faisant dire d’en couper ce qu’il y avait de meilleur et de plus mauvais, et de le lui renvoyer. Notre sage, fort habilement en ôta la langue, qu’il lui fit porter. Voilà ce qui lui a mérité l’estime et l’admiration de ce prince. - Ajoutez encore à cette première raison, dit '''Niloxène''', que '''Bias''' ne dédaigne pas, comme vous, l’amitié des rois ; car '''Amasis''' n’a pas moins d’estime pour vous ; il admire surtout la manière dont vous mesurâtes, avec la plus grande facilité et sans aucun instrument mathématique, la hauteur de la pyramide. En dressant votre bâton à l’extrémité de l’ombre qu’elle faisait sur la terre, le rayon solaire qui touchait le sommet de la pyramide et l’extrémité du bâton forma deux triangles ; et vous démontrâtes qu’il y avait la même proportion entre la hauteur du bâton et celle de la pyramide qu’entre la longueur des ombres projetées par l’une et par l’autre. Mais, comme je viens de le dire, on vous accuse, auprès de lui, d’être l’ennemi des rois, et on lui a rapporté plusieurs propos injurieux que vous avez tenus contre les tyrans [[#tyrans_NdT_DR|<span id="tyrans_NdT_DR_back"><sup>6</sup></span>]] ; entre autres que l’''Ionien'' '''Molpagore''' [[#Molpagore_NdT_DR|<span id="Molpagore_NdT_DR_back"><sup>7</sup></span>]] vous ayant demandé ce qui vous paraîtrait plus extraordinaire dans la vie, vous lui répondîtes : de voir vieillir un tyran. Une autre fois, comme on vint à parler, dans un repas, du naturel des animaux, vous dîtes que le plus méchant des animaux sauvages était le tyran, et des animaux domestiques, le flatteur. Les rois, quoiqu’ils affectent de ne rien avoir de commun avec les tyrans, n’aiment pas cependant ces sortes de discours. Cette dernière réponse, dit '''Thalès''', est de '''Pittacus''' ; il la fit un jour, en plaisantant, à ''Myrsile'' [[#Myrsile_NdT_DR|<span id="Myrsile_NdT_DR_back"><sup>8</sup></span>]]. Dans la première, je parlais d’un pilote, et non pas d’un tyran. Mais puisqu’on en a fait application au tyran, je dirai comme ce jeune homme qui, jetant une pierre à son chien, en avait atteint sa belle-mère : « Le coup n’est pas perdu. » Aussi jamais '''Solon''' ne montra-t-il plus de sagesse, à mon gré, que lorsqu’il refusa la tyrannie ; et si '''Pittacus''' n’eût pas été contraint de l’accepter, il n’eût point dit qu’il est à charge d’être vertueux [[#Pittacus_NdT_DR|<span id="Pittacus_NdT_DR_back"><sup>9</sup></span>]]. Il est vrai que '''Périandre''', qui a succédé à l’autorité des tyrans, paraît jusqu’ici opposer à ce mal héréditaire un remède puissant, par le soin qu’il a de rechercher les entretiens et les avis salutaires des hommes vertueux , et par l’horreur qu’il a témoignée pour le conseil barbare que notre compatriote '''Thrasybule''' lui donnait de faire mourir les grands. Un tyran qui veut commander à des esclaves, plutôt qu’à des hommes, ressemble à un laboureur qui aimerait mieux voir son champ couvert de passereaux et de sauterelles que d’orge et de froment. Le seul bien qui puisse compenser tant de maux attachés au pouvoir des tyrans, c’est d’avoir, même sur les plus grands et les plus vertueux de leurs sujets, la supériorité de l’honneur et de la vertu. Ceux qui préfèrent la sûreté à la gloire sont faits pour commander à des troupeaux, et non pour gouverner des hommes. « Mais '''Niloxène''' nous a jetés dans une conversation absolument étrangère à notre objet, et nous a fait négliger ce qui devait nous occuper en allant au banquet. Ne pensez-vous pas que les conviés ont, aussi bien que leur hôte, des apprêts à faire. Les ''Sybarites'', dit-on, prient les femmes à souper un an d’avance, afin qu’elles puissent préparer à loisir leurs habits et leurs bijoux [[#Sybarites_NdT_DR|<span id="Sybarites_NdT_DR_back"><sup>10</sup></span>]]. Pour moi, je pense qu’il faut encore plus de temps à un convive pour faire tous les préparatifs convenables, parcequ’il est bien plus difficile d’orner son esprit comme il faut, que de donner à son corps une parure vaine et superflue. Un homme sensé doit aller à un festin, non pour y remplir son estomac, comme un vase, mais pour écouter et tenir à son tour des propos utiles ou amusants, suivant les circonstances. C’est le seul moyen de rendre le repas agréable aux convives. En effet, on peut laisser un mauvais ragoût, et recourir à l’eau quand le vin n’est pas bon ; mais un convive désagréable, importun et fatigant, fait perdre tout le plaisir de la bonne chère et de la musique. On ne peut se délivrer de l’ennui qu’il cause, et souvent même une parole vive ou offensante qu’il se sera permise dans la liberté de la table, fait naître des aversions et des ressentiments qui ne finissent qu’avec la vie. Aussi '''Chilon''', invité hier à ce banquet, ne voulut-il accepter qu’après qu’on lui eut nommé tous les convives. Il disait avec raison, que quand on est sur mer ou dans un camp, il faut nécessairement supporter les compagnons qui nous sont associés, quelque fâcheux qu’ils soient ; mais dans un festin, il n’est pas d’un homme sensé de se mêler indifféremment avec toutes sortes de personnes. Le squelette que les ''Égyptiens'' placent ordinairement à côté d’eux dans leurs repas, en s’exhortant à penser qu’ils seront bientôt dans le même état, est, à la vérité, un compagnon de table assez triste et assez déplacé. Il est néanmoins utile, sinon pour les exciter au plaisir, du moins pour les porter à la bienveillance et à l’amitié réciproque, et pour les avertir de ne pas remplir d’aigreur et de querelles le temps si court de la vie [[#squelette_NdT_DR|<span id="squelette_NdT_DR_back"><sup>11</sup></span>]]. » </div> <table cellspacing=15 style="margin: 0 4em; font-size:85%;"> <tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Léchée_NdT_DR_back|<span id="Léchée_NdT_DR"><sup>1</sup></span>]] L’isthme de ''Corinthe'' avait deux ports, l’un sur la côte orientale, appelé ''Cenchrées'' ; et l’autre sur la côte occidentale, nommé ''Léchée''. Pausanias, qui a donné une description si détaillée de la ''Grèce'', parle d’un ''temple de Vénus'', bâti sur le port de ''Cenchrées'', et dont la statue était de marbre ; mais il ne fait pas mention de celui que cite Plutarque, et qu’il place près du port occidental. ''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Cratée_NdT_DR_back|<span id="Cratée_NdT_DR"><sup>2</sup></span>]] La mère de Périandre se nommait Cratée. On peut voir dans Parthenius la manière dont elle conçut et nourrit une passion incestueuse pour son fils, les moyens qu'elle prit pour la satisfaire sans être connue, et comment elle fut découverte. Les remords qu’elle en eut l’obligèrent de se donner la mort.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Mélisse_NdT_DR_back|<span id="Mélisse_NdT_DR"><sup>3</sup></span>]] Mélisse était fille de Proclès, roi d’[[w:Épidaure_(cité_antique)|''Épidaure'']]. On prétend que Périandre en devint éperdument amoureux pour l’avoir vue dans l’habillement ordinaire aux femmes du ''Péloponnèse''. Cette passion toutefois ne l’empêcha pas de partager son cœur entre elle et plusieurs concubines. Elles parvinrent à l’irriter contre elle au point qu’un jour, par un mouvement de jalousie, il la frappa d’un coup de pied, sans songer qu’elle était enceinte, et la fit mourir.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Niloxène_NdT_DR_back|<span id="Niloxène_NdT_DR"><sup>4</sup></span>]] Niloxène signifie hôte du ''Nil'' : ce nom semble indiquer que c’est un personnage supposé.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Priene_NdT_DR_back|<span id="Priene_NdT_DR"><sup>5</sup></span>]] Ville d’''Ionie'', colonie des ''Thébains'' et patrie de Bias.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#tyrans_NdT_DR_back|<span id="tyrans_NdT_DR"><sup>6</sup></span>]] Par le mot [[wikt:τύραννος#Grec_ancien|τύραννος]], que nous ne pouvons rendre en français que par celui de tyran, les ''Grecs'' entendaient, non pas seulement, comme nous, un prince injuste et cruel ; mais, en général, tout homme qui s’était emparé de l’autorité monarchique dans un état libre, sans qu’elle lui fût légitimement acquise.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Molpagore_NdT_DR_back|<span id="Molpagore_NdT_DR"><sup>7</sup></span>]] Ce Molpagore est peut-être le père d’Aristagore, que Darius établit tyran à ''Milet''.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Myrsile_NdT_DR_back|<span id="Myrsile_NdT_DR"><sup>8</sup></span>]] Myrsile n’est guère mieux connu que Molpagore. Strabon le met au nombre de ces ''Lesbiens'' ambitieux, qui, du temps de Pittacus, s’étaient emparés de l’autorité souveraine à ''Lesbos'', et dont le poète [[w:Alcée_de_Mytilène|Alcée]] avait flétri la mémoire.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Pittacus_NdT_DR_back|<span id="Pittacus_NdT_DR"><sup>9</sup></span>]] Pittacus en se voyant forcé, dans sa vieillesse, de reprendre le gouvernement des affaires, prononça cette maxime.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Sybarites_NdT_DR_back|<span id="Sybarites_NdT_DR"><sup>10</sup></span>]] Cet usage des ''Sybarites'' n’avait lieu que dans les festins publics.''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#squelette_NdT_DR_back|<span id="squelette_NdT_DR"><sup>11</sup></span>]] Cet usage, d'’abord particulier aux ''Egyptiens'', avait ensuite passé chez les ''Grecs'', de qui les ''Romains'' l'’empruntèrent. ''' </td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe III.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de la reconnaissance de la sagesse de '''Cléobuline''' ''« Eumétis »'' par '''Thalès''', de l’humilité de ce dernier et de son [[w:Cynisme|''cynisme'']].</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§3. Ἐν τοιούτοις λόγοις γενόμενοι κατὰ τὴν ὁδὸν ἀφικόμεθα πρὸς τὴν οἰκίαν, καὶ λούσασθαι μὲν ὁ Θαλῆς οὐκ ἠθέλησεν, ἀληλιμμένοι γὰρ ἦμεν· ἐπιὼν δὲ τούς τε δρόμους ἐθεᾶτο καὶ τὰς παλαίστρας καὶ τὸ ἄλσος τὸ παρὰ τὴν θάλατταν ἱκανῶς διακεκοσμημένον, ὑπ´ οὐδενὸς ἐκπληττόμενος τῶν τοιούτων, ἀλλ´ ὅπως μὴ καταφρονεῖν δοκοίη μηδ´ ὑπερορᾶν τοῦ Περιάνδρου τῆς φιλοτιμίας. τῶν δ´ ἄλλων τὸν ἀλειψάμενον ἢ λουσάμενον οἱ θεράποντες εἰσῆγον εἰς τὸν ἀνδρῶνα διὰ τῆς στοᾶς. Ὁ δ´ Ἀνάχαρσις ἐν τῇ στοᾷ καθῆστο, καὶ παιδίσκη προειστήκει τὴν κόμην ταῖς χερσὶ διακρίνουσα. ταύτην ὁ Θαλῆς ἐλευθεριώτατά πως αὐτῷ προσδραμοῦσαν ἐφίλησε καὶ γελάσας « οὕτως, » ἔφη, « ποίει καλὸν τὸν ξένον, ὅπως ἡμερώτατος ὢν μὴ φοβερὸς ᾖ τὴν ὄψιν ἡμῖν μηδ´ ἄγριος. » Ἐμοῦ δ´ ἐρομένου περὶ τῆς παιδὸς ἥτις εἴη, « τὴν σοφήν, » ἔφη, « καὶ περιβόητον ἀγνοεῖς Εὔμητιν; οὕτω γὰρ ταύτην ὁ πατὴρ αὐτός, οἱ δὲ πολλοὶ πατρόθεν ὀνομάζουσι Κλεοβουλίνην. » Καὶ ὁ Νειλόξενος εἶπεν « ἦ που τὴν περὶ τὰ αἰνίγματα δεινότητα καὶ σοφίαν, » ἔφη, « τῆς κόρης ἐπαινεῖς· καὶ γὰρ εἰς Αἴγυπτον ἔνια τῶν προβαλλομένων ὑπ´ αὐτῆς διῖκται. » « Οὐκ ἔγωγ´, » εἶπεν ὁ Θαλῆς· « τούτοις γὰρ ὥσπερ ἀστραγάλοις, ὅταν τύχῃ, παίζουσα χρῆται καὶ διαβάλλεται πρὸς τοὺς ἐντυχόντας. ἀλλὰ καὶ φρόνημα θαυμαστὸν καὶ νοῦς ἔνεστι πολιτικὸς καὶ φιλάνθρωπον ἦθος, καὶ τὸν πατέρα τοῖς πολίταις πραότερον ἄρχοντα παρέχει καὶ δημοτικώτερον. » « Εἶεν, » ὁ Νειλόξενος ἔφη, « καὶ φαίνεται βλέποντι πρὸς τὴν λιτότητα καὶ ἀφέλειαν αὐτῆς· Ἀνάχαρσιν δὲ πόθεν οὕτω τημελεῖ φιλοστόργως; » « Ὅτι, » ἔφη, « σώφρων ἀνήρ ἐστι καὶ πολυμαθής, καὶ τὴν δίαιταν αὐτῇ καὶ τὸν καθαρμόν, ᾧ χρῶνται Σκύθαι περὶ τοὺς κάμνοντας, ἀφθόνως καὶ προθύμως παραδέδωκε. καὶ νῦν οἶμαι περιέπειν αὐτὴν τὸν ἄνδρα καὶ φιλοφρονεῖσθαι, μανθάνουσάν τι καὶ προσδιαλεγομένην. » Ἤδη δὲ πλησίον οὖσιν ἡμῖν τοῦ ἀνδρῶνος ἀπήντησεν Ἀλεξίδημος ὁ Μιλήσιος (ἦν δὲ Θρασυβούλου τοῦ τυράννου νόθος) καὶ ἐξῄει τεταραγμένος καὶ σὺν ὀργῇ τινι πρὸς αὑτὸν οὐδὲν ἡμῖν γε σαφὲς διαλεγόμενος. ὡς δὲ τὸν Θαλῆν εἶδε, μικρὸν ἀνενεγκὼν καὶ καταστάς « οἵαν ὕβριν, » εἶπεν, « εἰς ἡμᾶς Περίανδρος ὕβρικεν, ἐκπλεῦσαι μὲν οὐκ ἐάσας ὡρμημένον ἀλλὰ προσμεῖναι δεηθεὶς τὸ δεῖπνον, ἐλθόντι δὲ νέμων κλισίαν ἄτιμον, Αἰολεῖς δὲ καὶ νησιώτας (καὶ τίνας γὰρ οὐχί;) Θρασυβούλου προτιμῶν· Θρασύβουλον γὰρ ἐν ἐμοὶ τὸν πέμψαντα προπηλακίσαι βουλόμενος καὶ καταβαλεῖν ὡς δὴ περιορῶν δῆλός ἐστιν. » « Εἶτ´, » ἔφη, « σὺ δέδιας μὴ καθάπερ Αἰγύπτιοι τοὺς ἀστέρας ὑψώματα καὶ ταπεινώματα λαμβάνοντας ἐν τοῖς τόποις οὓς διεξίασι γίγνεσθαι βελτίονας ἢ χείρονας ἑαυτῶν λέγουσιν, οὕτως ἡ περὶ σὲ διὰ τὸν τόπον ἀμαύρωσις ἢ ταπείνωσις γένηται; καὶ τοῦ Λάκωνος ἔσῃ φαυλότερος, ὃς ἐν χορῷ τινι κατασταθεὶς εἰς τὴν ἐσχάτην χώραν ὑπὸ τοῦ ἄρχοντος ‘εὖ γ´,’ εἶπεν, ‘ἐξεῦρες, ὡς καὶ αὕτα ἔντιμος γένηται.’ οὐ καταλαβόντας, » ἔφη, « τόπον μετὰ τίνας κατακείμεθα δεῖ ζητεῖν, μᾶλλον δ´ ὅπως εὐάρμοστοι τοῖς συγκατακειμένοις ὦμεν, ἀρχὴν καὶ λαβὴν φιλίας εὐθὺς ἐν αὐτοῖς ζητοῦντες, μᾶλλον δ´ ἔχοντες τὸ μὴ δυσκολαίνειν ἀλλ´ ἐπαινεῖν ὅτι τοιούτοις συγκατεκλίθημεν· ὡς ὅ γε τόπῳ κλισίας δυσχεραίνων δυσχεραίνει τῷ συγκλίτῃ μᾶλλον ἢ τῷ κεκληκότι, καὶ πρὸς ἀμφοτέρους ἀπεχθάνεται. » « Λόγος, » ἔφη, « ταῦτ´ ἄλλως ἐστίν » ὁ Ἀλεξίδημος, « ἔργῳ δὲ καὶ τοὺς σοφοὺς ὑμᾶς ὁρῶ τὸ τιμᾶσθαι διώκοντας, » καὶ ἅμα παραμειψάμενος ἡμᾶς ἀπῆλθε. Καὶ ὁ Θαλῆς πρὸς ἡμᾶς τὴν ἀτοπίαν τοῦ ἀνθρώπου θαυμάζοντας, « ἔμπληκτος, » ἔφη, « καὶ ἀλλόκοτος φύσει, ἐπεὶ καὶ μειράκιον ὢν ἔτι, μύρου σπουδαίου Θρασυβούλῳ κομισθέντος, εἰς ψυκτῆρα κατεράσας μέγαν καὶ προσεγχέας ἄκρατον ἐξέπιεν, ἔχθραν ἀντὶ φιλίας Θρασυβούλῳ διαπεπραγμένος. » Ἐκ τούτου περιελθὼν ὑπηρέτης « κελεύει σε Περίανδρος, » ἔφη, « καὶ Θαλῆν παραλαβόντα τοῦτον ἐπισκέψασθαι τὸ κεκομισμένον ἀρτίως αὐτῷ πότερον ἄλλως γέγονεν ἤ τι σημεῖόν ἐστι καὶ τέρας· αὐτὸς μὲν γὰρ ἔοικε τεταράχθαι σφόδρα, μίασμα καὶ κηλῖδα τῆς θυσίας ἡγούμενος. » ἅμα δ´ ἀπῆγεν ἡμᾶς εἴς τι οἴκημα τῶν περὶ τὸν κῆπον. ἐνταῦθα νεανίσκος ὡς ἐφαίνετο νομευτικός, οὔπω γενειῶν ἄλλως τε τὸ εἶδος οὐκ ἀγεννής, ἀναπτύξας τινὰ διφθέραν ἔδειξεν ἡμῖν βρέφος ὡς ἔφη γεγονὸς ἐξ ἵππου, τὰ μὲν ἄνω μέχρι τοῦ τραχήλου καὶ τῶν χειρῶν ἀνθρωπόμορφον, τὰ λοιπὰ δ´ ἔχον ἵππου, τῇ δὲ φωνῇ καθάπερ τὰ νεογνὰ παιδάρια κλαυθμυριζόμενον. ὁ μὲν οὖν Νειλόξενος, « Ἀλεξίκακε » εἰπών, ἀπεστράφη τὴν ὄψιν, ὁ δὲ Θαλῆς προσέβλεπε τῷ νεανίσκῳ πολὺν χρόνον, εἶτα μειδιάσας (εἰώθει δ´ ἀεὶ παίζειν πρὸς ἐμὲ περὶ τῆς τέχνης) « ἦ που τὸν καθαρμόν, ὦ Διόκλεις, » ἔφη, « κινεῖν διανοῇ καὶ παρέχειν πράγματα τοῖς ἀποτροπαίοις, ὥς τινος δεινοῦ καὶ μεγάλου συμβάντος; » « Τί δ´, » εἶπον, « οὐ μέλλω; στάσεως γάρ, ὦ Θαλῆ, καὶ διαφορᾶς τὸ σημεῖόν ἐστι, καὶ δέδια μὴ μέχρι γάμου καὶ γενεᾶς ἐξίκηται, πρὶν ἢ τὸ πρῶτον ἐξιλάσασθαι μήνιμα, τῆς θεοῦ δεύτερον ὡς ὁρᾷς προφαινούσης. » Πρὸς τοῦτο μηδὲν ἀποκρινάμενος ὁ Θαλῆς ἀλλὰ γελῶν ἀπηλλάττετο. καὶ τοῦ Περιάνδρου πρὸς τὰς θύρας ἀπαντήσαντος ἡμῖν καὶ διαπυθομένου περὶ ὧν εἴδομεν, ἀφεὶς ὁ Θαλῆς με καὶ λαβόμενος τῆς ἐκείνου χειρὸς ἔφη, « ἃ μὲν Διοκλῆς κελεύει δράσεις καθ´ ἡσυχίαν· ἐγὼ δέ σοι παραινῶ νέοις οὕτω μὴ χρῆσθαι νομεῦσιν ἵππων, ἢ διδόναι γυναῖκας αὐτοῖς. » Ἔδοξε μὲν οὖν μοι τῶν λόγων ἀκούσας ὁ Περίανδρος ἡσθῆναι σφόδρα· καὶ γὰρ ἐξεγέλασε καὶ τὸν Θαλῆν περιβαλὼν κατησπάσατο. κἀκεῖνος « οἶμαι δ´, » εἶπεν, « ὦ Διόκλεις, καὶ πέρας ἔσχε τὸ σημεῖον· ὁρᾷς γὰρ ἡλίκον κακὸν γέγονεν ἡμῖν, Ἀλεξιδήμου συνδειπνεῖν μὴ θελήσαντος. » </div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §3'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§3. Ce fut en tenant de tels propos chemin faisant, que nous arrivâmes à la maison. '''Thalès''' ne voulut pas se baigner : « Je me suis frotté d’huile. » dit-il. Il se promena donc de côté et d’autre, regardant les champs de course, les palestres, et le bois sacré, voisin de la mer, que l’on avait bien convenablement disposé. Ce n’était pas qu’il fût frappé par aucun de ces préparatifs, mais il voulait ne pas avoir l’air de mépriser '''Périandre''' et de faire peu de cas de l’empressement avec lequel celui-ci honorait ses hôtes. Pour les autres convives, à mesure qu’ils s’étaient parfumés ou baignés, les serviteurs les introduisaient par la galerie dans la salle du banquet. Or '''Anacharsis''' s’était installé dans cette galerie, et devant lui une jeune fille se tenait, lui séparant les cheveux avec ses mains. Lorsqu’entra '''Thalès''', elle s’élança très librement à sa rencontre, et '''Thalès''', après l’avoir embrassée, lui dit en riant : « Continue à rendre bien beau notre étranger, afin qu’étant devenu la douceur même il ne conserve pas au milieu de nous une mine à faire peur et un aspect sauvage. » Je lui demandai quelle était cette jeune enfant : « Quoi ! » me dit-il, « vous ne connaissez pas la savante et célèbre '''Eumétis''' ! car c’est ainsi que son père la nomme : le plus communément on l’appelle '''Cléobuline''', du nom paternel. » Et '''Niloxène''' : « C’est sans doute à cause de son talent et de son habileté pour les énigmes, que vous faites l’éloge de cette jeune fille : car quelques-unes de celles qu’elle a proposées sont parvenues jusqu’en ''Égypte''. » — « Ce n’est pas à cause de cela », répondit '''Thalès''' : « les énigmes sont pour elle des joujoux dont elle s’amuse à l’occasion pour faire sa partie avec ceux qui se rencontrent. Mais ce qui est admirable en elle c’est sa profondeur d’esprit, son sens politique, l’aménité de son caractère, et le talent qu’elle a de rendre plus douce l’autorité de son père et d’inspirer à celui-ci des sentiments plus humains à l’égard du peuple. » — « Soit », dit '''Niloxène''' ; « et cela se reconnaît à voir sa modestie et sa simplicité. Mais d’où vient qu’elle prend un soin si amoureux de la toilette d’'''Anacharsis''' ? » — Parce que c’est, répondit '''Thalès''', « un sage, un homme des plus instruits, et parce qu’il lui a communiqué, avec de nombreux détails et de grand coeur, l’ensemble des pratiques sanitaires et des purifications que les ''Scythes'' appliquent au traitement des malades. Et dans ce moment je suppose qu’elle l’entoure de soins et d’amitiés parce qu’elle s’instruit de quelque chose en conversant avec lui. » Comme nous étions déjà près de la salle, '''Alexidème''' le ''Milésien'' vint à notre rencontre. C’était un bâtard du tyran '''Thrasybule'''. Il était sorti tout troublé, et avec une sorte de fureur il se parlait à lui-même, mais ses paroles n’avaient rien de clair pour nous. Quand il eut vu '''Thalès''', il se remit un peu ; puis, s’arrêtant tout court : « Quel affront '''Périandre''' vient de nous faire ! Je voulais mettre à la voile : il ne l’a pas permis ; il m’a supplié de rester à son festin, et quand j’arrive, il me donne une des dernières places, faisant passer des [[w:Éoliens|''Éoliens'']], des insulaires (je ne sais qui il ne me préfère pas), les faisant passer, dis je, avant '''Thrasybule''', car c’est '''Thrasybule''' en ma personne, c’est celui par lequel je suis envoyé qu’il a l’intention de traîner dans la boue et de ravaler comme le méprisant : la chose est bien claire. » — « Eh quoi ! » lui dit '''Thalès''', « êtes-vous comme les ''Égyptiens'', qui prétendent que les astres, suivant qu’ils prennent une position élevée ou basse en parcourant leur orbite, ont une condition meilleure ou pire qu’ils ne l’avaient ? Craignez-vous, pareillement, qu’autour de vous, en raison de la place où vous serez, il ne se produise obscurité ou dépression ? Serez-vous moins résigné que certain ''Spartiate'' ? À je ne sais quelle représentation il avait été placé au dernier rang par le maître des cérémonies : « Voilà qui va bien », lui dit-il : « tu as trouvé moyen de rendre honorable ce lieu même. Quand nous avons pris une place nous ne devons pas chercher au-dessous de qui nous sommes installés, mais plutôt comment nous nous mettrons en bon accord avec nos voisins. À leur occasion nous ferons voir tout d’abord, ou plutôt nous l’éprouverons réellement, un désir d’initiative et de prise de possession en matière d’amitié, et nous manifesterons ce désir en nous félicitant, loin d’en montrer du dépit, de ce que l’on nous a placés en une telle compagnie. Mais celui qui se plaint du rang qu’on lui donne à table montre plus de mécontentement contre son commensal que contre son hôte, et il se rend odieux à l’un et à l’autre. » — « Paroles que tout cela, » dit '''Alexidème''', « et paroles sans portée ! Mais je vois que de fait, vous autres sages, vous recherchez aussi les honneurs. » En même temps il s’éloigna de nous et disparut. Une conduite si étrange nous semblait étonnante. « C’est », nous dit '''Thalès''', « un homme écervelé et naturellement bizarre. Vous allez en juger. Il était encore tout jeune ; on avait apporté à '''Thrasybule''' un parfum d’un très haut prix. '''Alexidème''' le versa dans un grand vase à rafraîchir, y mêla du vin pur, et avala le tout, rendant son père odieux au lieu de le faire aimer. » Au même moment parut un serviteur : « Vous êtes invité par '''Périandre''' », me dit-il, « à venir, en vous faisant accompagner de '''Thalès''' que voici, examiner l’objet qu’on lui a récemment apporté, afin que vous disiez si c’est une création toute fortuite, ou bien un présage et une monstruosité; car, pour ce qui est de '''Périandre''', il a l’air grandement troublé, pensant que c’est une souillure et une profanation pour son sacrifice. » En même temps, il nous emmena vers une des salles qui donnaient sur le jardin. Là un jeune homme paraissant appartenir à la classe des bergers, qui n’avait pas encore de barbe et dont la physionomie ne manquait pas de noblesse, déploya une espèce de couverture en cuir, et nous montra un petit enfant né, disait-il, d’une cavale. Par le haut, jusqu’au cou et aux mains, cette créature était de forme humaine ; mais le reste était d’un cheval, et sa voix avait quelque chose des vagissements poussés par les petits enfants qui viennent de naître. « Dieu préservateur ! » s’écria '''Niloxène''', et il détourna les yeux. Mais '''Thalès''' fixa longtemps son regard sur le jeune pâtre; puis s’étant mis à sourire, (car il avait l’habitude de plaisanter toujours avec moi sur ma profession) : « Eh bien, '''Dioclès''', » dit-il, « songez-vous à préparer quelque expiation, et à donner de la besogne aux dieux préservateurs, comme vous trouvant en présence de quelque événement grave et considérable ? » — « Pourquoi non ? » répondis-je : « j’y vois le présage de troubles et de discordes qui s’étendront, j’en ai peur, jusqu’à un mariage et une génération avant que le courroux de la déesse ait été apaisé, puisqu’elle fait, vous le savez, une seconde manifestation ». À ces paroles '''Thalès''' ne répondit rien : il se contenta de rire et de s’en aller. Et comme '''Périandre''' était à la porte, s’avançant pour nous interroger sur ce que nous venions de voir, '''Thalès''' me quitta et le prit par la main : « Ce que '''Dioclès''' prescrira, vous l’exécuterez à loisir », lui dit-il ; « mais moi j’ai un conseil à vous donner : pour faire paître vos juments ne prenez pas de jeunes garçons, ou bien à ceux-ci donnez des femmes. » Ces paroles me semblèrent causer, des qu’il les eut entendues, une vive satisfaction à '''Périandre''', car il éclata de rire et serra très affectueusement '''Thalès''' dans ses bras. '''Thalès''' alors : « '''Dioclès''' », me dit-il, « si je conjecture bien, le prodige reçoit déjà son accomplissement. Car vous voyez quel grand malheur nous est arrivé, '''Alexidème''' n’ayant pas voulu souper avec nous. »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §3'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">Après avoir ainsi conversé pendant le chemin, nous arrivâmes au lieu destiné pour le banquet. '''Thalès''' ne voulut point entrer dans le bain, parcequ'’il s'’était déja parfumé, mais il se promena dans les dehors, s'’arrêta à voir les luttes et les courses ; il alla voir le bocage voisin de la mer, qu'’on avait décoré avec beaucoup de soin, non qu'’il fût frappé de tout cet appareil, mais pour ne point paraître mépriser '''Périandre''' et dédaigner sa magnificence. Tous les autres convives, à mesure qu'’ils sortaient du bain, étaient introduits par des esclaves dans la salle du banquet. '''Anacharsis''' seul était assis sous le portique, une jeune fille, debout, lui arrangeait les cheveux. Dès qu'’elle vit '''Thalès''', elle accourut très librement au-devant de lui ; ce philosophe l'’embrassa, et lui dit en riant : « Parez cet hôte du mieux que vous pourrez, afin que le plus doux des hommes n'’ait plus un extérieur sauvage et effrayant. » Je lui demandai quelle était cette jeune personne : « Eh quoi ! me répondit-il, vous ne connaissez pas la sage et fameuse '''Eumétis''' ? C'’est le nom que '''Cléobule''' lui donne, mais les autres l'’appellent '''Cléobuline''', du nom de son père. - Est-ce, dit '''Niloxène''', à cause de sa subtilité et de son adresse à composer des énigmes, que vous faites ainsi son éloge ? Quelques unes de celles qu'’on lui attribue ont pénétré jusqu'’en Egypte. - Non, répliqua '''Thalès''' ; ces énigmes ne sont que des jouets dont elle s'’amuse dans l'’occasion pour éprouver la perspicacité de ceux qu'’elle rencontre. Ce qu'’elle a de vraiment admirable, c'’est sa grande prudence, sa capacité pour les affaires, son humanité, et l'’attention qu'’elle a de rendre le gouvernement de son père doux et bienfaisant. - Il est vrai, reprit '''Niloxène''', que sa modestie et sa simplicité annoncent combien elle est populaire. Mais d'’où viennent ces soins et cet intérêt pour '''Anacharsis''' ? - Parceque c'’est un homme sensé et très instruit, répondit '''Thalès''', qu'’il s'’est fait un plaisir de lui apprendre en détail le régime et le traitement que les ''Scythes'' emploient dans les maladies. Je suis sûr que dans ce moment même où elle le pare avec tant de soin, elle s'’entretient utilement avec lui, et cherche à s'’instruire. » Nous approchions de la salle, lorsque nous rencontrâmes '''Alexidème''' de ''Milet'', fils naturel du tyran '''Thrasybule'''. Il sortait tout troublé, et d'’un ton de colère murmurait quelques mots que nous ne pûmes entendre. Dès qu'’il vit '''Thalès''', il se remit un peu ; et en lui adressant la parole, il lui dit : « Quel indigne affront '''Périandre''' vient de nous faire ! J'’allais partir, il me retient, il me prie de rester à son festin ; et quand j'’arrive, il m'’y donne la dernière place : il préfère un ''éolien'', un insulaire, enfin je ne sais qui, à '''Thrasybule''', car il est évident que c'’est '''Thrasybule''' que '''Périandre''' méprise, et brave ouvertement dans la personne de son envoyé [[#Thrasybule_NdT_DR|<span id="Thrasybule_NdT_DR_back"><sup>1</sup></span>]]. - Eh quoi ! lui dit '''Thalès''', craignez-vous que la place que vous occuperez à table ne vous rende plus grand ou plus petit, comme les ''Égyptiens'' disent que les astres brillent plus ou moins, suivant que le cercle qu'’ils parcourent est plus ou moins élevé ? Et vous estimeriez-vous moins que ce ''Lacédémonien'', qui, dans une assemblée publique, placé par le magistrat au dernier rang, lui dit : Vous avez trouvé le moyen de rendre cette place honorable [[#place_honorable_NdT_DR|<span id="place_honorable_NdT_DR_back"><sup>2</sup></span>]] ? Doit-on en effet examiner après qui l'’on est placé ? Ne faut-il pas plutôt s'’accommoder de ceux qu'’on a pour voisins, afin d'’avoir une occasion naturelle de se lier avec eux, et loin de se fâcher du rang qu'’on occupe, témoigner sa satisfaction de se trouver auprès d'’eux ? Celui qui se plaint de la place qu'’on lui assigne semble plus mécontent de son voisin que de son hôte, et se rend odieux à l'’un et à l'’autre . - Belles paroles que cela, repartit '''Alexidème'''. Mais, dans le fait, je vois que vous autres sages, vous recher- chezles honneurs aussi bien que nous. » En même temps, il nous quitte brusquement, et s'en va. Une conduite si étrange nous étonna tous : « Ce jeune homme, nous dit Thalès, a montré dès l'enfance un caractère dur et emporté. Unjour qu'on avait fait présent à Thrasybule d'un parfum de grand prix, il le versa dans une coupe mêlé avec du vin, et l'avala, payant ainsi d'in- gratitude la tendresse de son père. >>> Au même instant, un des gens de Périandre vint me prier d'aller avec Thalès voir quelque chose d'extraordi- naire qu'on venait de lui apporter, afin de juger si c'é- tait un jeu de la nature ou un prodige menaçant ; il nous dit qu'il en était tout troublé, et qu'il craignait que son sacrifice n'en fùt souillé. En même temps, il nous con- duit dans une salle qui donnait sur le jardin. Nous y trou- vâmes un jeune homme sans barbe, d'une figure intéres- sante, qui avait l'air d'un berger. Il lève une espèce de peau, et nous fait voir un enfant qu'il disait être né d'une cavale, qui avait la tête, le cou et les mains d'un homme, et dans tout le reste, était fait comme un cheval. Sa voix ressemblait à celle d'un enfant qui vient de naître : « Dieu préservateur ! s'écria Niloxène, en détournant la vue. >>> Mais Thalès, qui a coutume de plaisanter avec moi sur mon art , après avoir longtemps considéré ce jeune homme, me dit en souriant : « Ne pensez-vous pas déja, Dioclès, à faire des expiations ? Et pour détourner un présage si terrible , n'allez-vous pas donner bien de l'ouvrage aux dieux préservateurs ? -Pourquoi non ? lui répondis-je. Ce prodige, Thalès, est un signe menaçant de troubles et de discorde ; et je crains bien que l'épouse et les enfants de Périandre n'en éprouvent les tristes suites, puisque enfin, comme vous voyez, avant que la déesse soit apaisée, elle donne une nouvelle marque de son courroux. » A ces mots, Thalès sourit et sortit sans rien répondre. Il vit , à la porte de la salle, Périandre venir au-devant de nous, pour savoir notre sentiment. Il me quitta, lui prit la main, et lui dit : « Dioclès vous parlera ; vous ferez à loisir ce qu'il vous dira. Pour moi, je pense que vous ne devez pas avoir, pour garder vos juments, des bergers aussi jeunes, ou que vous devez les marier. » Cediscours parut faire grand plaisir à Périandre , car il en rit beaucoup, et embrassa affectueusement Thalès. Celui-ci s'’adressant à moi : « Je crois, '''Dioclès''', me dit-il, que le prodige a déja eu son accomplissement. Vous voyez le malheur qui vient de nous arriver ; '''Alexidème''' a refusé de souper avec nous. » </div> <table cellspacing=15 style="margin: 0 4em; font-size:85%;"> <tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#Thrasybule_NdT_DR_back|<span id="Thrasybule_NdT_DR"><sup>1</sup></span>]]Dans les ''Propos de table'', on trouve une aventure pareille, arrivée chez Timon, frère de Plutarque ; mais le personnage n’est pas nommé. ''' </td> <tr> </tr> <td valign=top style="text-align: justify; text-indent: 15px">'''[[#place_honorable_NdT_DR_back|<span id="place_honorable_NdT_DR"><sup>2</sup></span>]] Plutarque, dans les ''apophthegmes des Lacédémoniens'', nomme ce ''Spartiate'' Dæmonidas, et attribue un mot semblable au fameux Agésilas. ''' </td> </tr> </table> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe IV.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage du caractère désinvolte de '''Thalès'''.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§4. Ἐπεὶ δ´ εἰσήλθομεν, ἤδη μεῖζον ὁ Θαλῆς φθεγξάμενος « ποῦ δ´ » εἶπεν « ὁ ἀνὴρ κατακλινάμενος ἐδυσχέρανεν; » ἀποδειχθείσης δὲ τῆς χώρας περιελθὼν ἐκεῖ κατέκλινεν ἑαυτὸν καὶ ἡμᾶς « ἀλλὰ κἂν ἐπριάμην » εἰπών « Ἀρδάλῳ κοινωνεῖν μιᾶς τραπέζης. » ἦν δὲ Τροιζήνιος ὁ Ἄρδαλος, αὐλῳδὸς καὶ ἱερεὺς τῶν Ἀρδαλείων Μουσῶν, ἃς ὁ παλαιὸς Ἄρδαλος ἱδρύσατο ὁ Τροιζήνιος.<br /><p style="text-align: centre">[...]<br /><p style="text-align: justify; text-indent: 15px">καὶ ὁ Θαλῆς ἐμὲ προσαγορεύσας ἐπάνω τοῦ Βίαντος κατακείμενον « τί οὐκ ἔφρασας, » εἶπεν, « ὦ Διόκλεις, Βίαντι τὸν Ναυκρατίτην ξένον ἥκοντα μετὰ προβλημάτων βασιλικῶν αὖθις ἐπ´ αὐτόν, ὅπως νήφων καὶ προσέχων ἑαυτῷ τὸν λόγον δέχηται; » Καὶ ὁ Βίας « ἀλλ´ οὗτος μέν, » ἔφη, « πάλαι δεδίττεται ταῦτα παρακελευόμενος, ἐγὼ δὲ τὸν Διόνυσον οἶδα τά τ´ ἄλλα δεινὸν ὄντα καὶ Λύσιον ἀπὸ σοφίας προσαγορευόμενον, ὥστ´ οὐ δέδια τοῦ θεοῦ μεστὸς γενόμενος μὴ ἀθαρσέστερον ἀγωνίσωμαι. » Τοιαῦτα μὲν ἐκεῖνοι πρὸς ἀλλήλους ἅμα δειπνοῦντες ἔπαιζον·</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §4'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§4. Après que nous fûmes entrés, '''Thalès''' ayant déjà élevé plus haut la voix: « Où donc », dit-il, « l’avait-on placé, cet homme qui s’en est formalisé ? » Quand on lui eut montré l’endroit il fit le tour de la salle, et ce fut là-même qu’il se plaça et nous installa. « En vérité », ajouta-t-il, « j’aurais payé pour partager la même table qu’'''Ardalus'''. » Cet '''Ardalus''' était un joueur de flûte [[w:Trézène_(ville)|''Trézénien'']] [[#Trézène|<span id="Trézène_back"><sup>'''I'''</sup></span>]], prêtre des ''muses Ardalides'' auxquelles l’antique [[w:Ardalos|'''Ardalus''']] de ''Trézène'' avait dressé des statues.<br /><p style="text-align: centre">[...]<br /><p style="text-indent: 15px">Moi, j’étais au-dessus de '''Bias''', et '''Thalès''' m’ayant interpellé : « '''Dioclès''' », me dit-il, « pourquoi n’avez-vous pas dit à '''Bias''' que l’étranger de ''Naucratie'' est venu une seconde fois le trouver avec des questions de la part de son prince, afin que ce soit à jeun qu’il reçoive ces communications et qu’il y applique son esprit ? » Et '''Bias''' : « Il y a longtemps », dit-il, « que '''Dioclès''' me menace de m’y contraindre ; mais je sais que '''Bacchus''', entre autres attributions merveilleuses, possède une sagacité qui l’a fait surnommer le dieu qui délie, de sorte que je ne crains pas, étant rempli de la divinité, que la confiance m’abandonne au moment de la lutte. » C’est ainsi que, pendant le repas, ils échangeaient entre eux des plaisanteries.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §4'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> {{Boîte déroulante début|titre=NdA trad. par Victor Bétolaud de 1870|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Trézène_back|<span id="Trézène"><sup>I</sup></span>]] Du nom propre grec ancien Τροιζήν / Troizḗn [[wikt:en:Τροιζήν#Ancient_Greek|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px">[[w:polis|Cité grecque]] du [[w:Péloponnèse|''Péloponnèse'']], sur la côte nord de l’[[w:Argolide|Argolide]].'''<br/><br/></div> {{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe VII.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Réflexions de '''Thalès''' sur le défi de sagacité posé par le roi des ''Éthiopiens'' au roi d’''Égypte'', [[w:Ahmôsis_II|'''Amasis''']], consistant à boire la mer. Ce dernier fait appel à '''Bias''' pour la résoudre, et celui-ci souhaite l’examiner en commun avec les sages. '''Chilon''' explique qu’il ne s’agit pas de faire disparaître tant d’eau salée, mais de rendre la domination d’'''Amasis''' potable et douce à ses sujets (§6).</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§7. [...] Ἐπὶ τούτῳ δ´ ὁ '''Θαλῆς''' ἔφησεν, εὐδαιμονίαν ἄρχοντος νομίζειν, εἰ τελευτήσειε γηράσας κατὰ φύσιν.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §7'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§7. [...] Après lui '''Thalès''' prit la parole : « J’estime que le bonheur pour un souverain, c’est s’il meurt de vieillesse et naturellement. ».</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §7'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe IX.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Réflexions de '''Thalès''' sur le défi de sagacité posé par le roi d’''Égypte'', [[w:Ahmôsis_II|'''Amasis''']], au roi des ''Éthiopiens'', consistant en une série de questions : « Qu’y a-t-il de plus ancien ? Le temps. — De plus grand ? Le monde. — De plus habile ? La vérité. — De plus beau ? La lumière. — De plus commun ? La mort. — De plus utile ? Dieu. — De plus nuisible ? Le mauvais Génie. — De plus puissant ? La Fortune. — De plus facile ? Le plaisir. ». '''Amasis''' fait encore appel à '''Bias''' pour analyser les réponses du roi des ''Éthiopiens'', qui les examinent également en commun avec les sages.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§9. Τούτων πάλιν ἀναγνωσθέντων, ὦ Νίκαρχε, γενομένης σιωπῆς Θαλῆς ἠρώτησε τὸν Νειλόξενον εἰ προσήκατο τὰς λύσεις ὁ Ἄμασις. ἐκείνου δ´ εἰπόντος ὅτι τὰς μὲν ἀπεδέξατο ταῖς δ´ ἐδυσκόλαινε, « καὶ μὴν οὐδέν, » εἶπεν ὁ Θαλῆς, « ἀνεπίληπτόν ἐστιν, ἀλλ´ ἔχει πάντα διαμαρτίας μεγάλας καὶ ἀγνοίας. οἷον εὐθὺς ὁ χρόνος πῶς ἂν εἴη πρεσβύτατον, εἰ τὸ μὲν αὐτοῦ γεγονὸς τὸ δ´ ἐνεστώς ἐστι τὸ δὲ μέλλον; ὁ γὰρ μεθ´ ἡμᾶς ἐσόμενος χρόνος καὶ πραγμάτων τῶν νῦν καὶ ἀνθρώπων νεώτερος ἂν φανείη. τὸ δὲ τὴν ἀλήθειαν ἡγεῖσθαι σοφίαν οὐδὲν ἐμοὶ δοκεῖ διαφέρειν τοῦ τὸ φῶς ὀφθαλμὸν ἀποφαίνειν. εἰ δὲ τὸ φῶς καλόν, ὥσπερ ἐστὶν, ἐνόμιζε, πῶς τὸν ἥλιον αὐτὸν παρεῖδε; τῶν δ´ ἄλλων ἡ μὲν περὶ θεῶν καὶ δαιμόνων ἀπόκρισις θράσος ἔχει καὶ κίνδυνον, ἀλογίαν δὲ καὶ πολλὴν ἡ περὶ τῆς τύχης· οὐ γὰρ ἂν μετέπιπτε ῥᾳδίως οὕτως, ἰσχυρότατον οὖσα τῶν ὄντων καὶ ῥωμαλεώτατον. οὐ μὴν οὐδ´ ὁ θάνατος κοινότατόν ἐστιν· οὐ γάρ ἐστι πρὸς τοὺς ζῶντας. ἀλλ´ ἵνα μὴ δοκῶμεν εὐθύνειν τὰς τῶν ἑτέρων ἀποφάσεις, ἰδίας ταῖς ἐκείνου παραβάλωμεν· ἐμαυτὸν δὲ παρέχω πρῶτον, εἰ βούλεται Νειλόξενος, ἐρωτᾶν καθ´ ἕκαστον. ὡς οὖν ἐγένοντο τότε, κἀγὼ νῦν διηγήσομαι τὰς ἐρωτήσεις καὶ τὰς ἀποκρίσεις· ‘Τί πρεσβύτατον;’ [[wikt:en:θεός#Ancient_Greek|‘θεός,’]] » ἔφη Θαλῆς· « ‘ἀγέν νητον γάρ ἐστι.’ ‘Τί μέγιστον;’ ‘τόπος· τἄλλα μὲν γὰρ ὁ κόσμος, τὸν δὲ κόσμον οὗτος περιέχει.’ ‘Τί κάλλιστον;’ ‘κόσμος· πᾶν γὰρ τὸ κατὰ τάξιν τούτου μέρος ἐστί.’ ‘Τί σοφώτατον;’ ‘χρόνος· τὰ μὲν γὰρ εὕρηκεν οὗτος ἤδη, τὰ δ´ εὑρήσει.’ ‘Τί κοινότατον;’ ‘ἐλπίς· καὶ γὰρ οἷς ἄλλο μηδέν, αὕτη πάρεστι.’ ‘Τί ὠφελιμώτατον;’ ‘ἀρετή· καὶ γὰρ τἄλλα τῷ χρῆσθαι καλῶς ὠφέλιμα ποιεῖ.’ ‘Τί βλαβερώτατον;’ ‘κακία· καὶ γὰρ τὰ πλεῖστα βλάπτει παραγενομένη.’ ‘Τί ἰσχυρότατον;’ ‘ἀνάγκη· μόνον γὰρ ἀνίκητον.’ ‘Τί ῥᾷστον;’ ‘τὸ κατὰ φύσιν, ἐπεὶ πρὸς ἡδονάς γε πολλάκις ἀπαγορεύουσιν.’ » </div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §9'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§9. Cette lecture ainsi reproduite, mon cher '''Nicarque''', il se fit un moment de silence. Après quoi '''Thalès''' demanda à '''Niloxène''' si '''Amasis''' avait accepté de telles solutions. Il répondit que ce monarque avait accepté les unes et qu’il avait été mécontent des autres. « C’est qu’en effet », dit '''Thalès''', « il n’en est aucune qui soit irréprochable, et toutes sont grandement entachées d’erreur et d’ignorance. Ainsi, d’abord, comment le temps pourrait-il être ce qu’il y a de plus ancien, puisque, une partie étant écoulée, je le veux bien, une autre est le présent, une autre est l’avenir ? Le temps qui doit venir après nous est évidemment plus jeune que les hommes d’aujourd’hui, que les événements actuels. Croire que ce qu’il y a de plus habile, ce soit la vérité, c’est, à mon avis, ne pas émettre une autre opinion que celle-ci : l’oeil et la lumière sont tout un. Si du reste l’''Éthiopien'' a cru, ce qui est réel, la lumière plus belle que tout, pourquoi a-t-il négligé de nommer le soleil lui-même ? Des autres réponses, celle qui concerne la Divinité et le Génie est aussi téméraire que dangereuse ; et ce qu’il dit de la Fortune est tout à fait déraisonnable : car elle ne changerait pas avec tant de facilité, si elle était ce qu’il y a de plus puissant et de plus fort au monde. De même, la mort n’est pas ce qu’il y a de plus commun, puisqu’elle n’est pas commune aux vivants. Mais pour que nous ne semblions pas nous borner à redresser les réponses des autres, il faut y opposer nos propres solutions. Je m’y offre le premier, si '''Niloxène''' veut reprendre chaque question ». Telles que furent faites alors et les demandes et les réponses, je vais vous les reproduire aujourd’hui : Qu’y a-t-il de plus ancien ? C’est dieu, répondit '''Thalès''', attendu qu’il est incréé. — De plus grand ? L’espace : car si le monde contient le reste, à son tour il est contenu dans l’espace. — De plus beau ? Le monde : car tout ce qui est bien ordonné en fait partie. ― De plus habile ? Le temps : car c’est lui qui a découvert et qui découvrira tout. — De plus commun ? L’espérance : car ceux même qui n’ont rien autre chose la possèdent. — De plus utile ? La vertu : car elle rend toutes les autres choses utiles par le bon usage qu’elle en fait. — De plus nuisible ? Le vice : car il corrompt tout par sa présence. — De plus puissant ? La nécessité : car elle est seule invincible. De plus facile ? Ce qui est selon la nature : car, pour ce qui est du plaisir, il amène souvent la lassitude.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §9'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe XI.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Réflexion de '''Thalès''' sur les gouvernements où la loi est égale pour tous.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§11. Ἐπὶ τούτῳ '''Θαλῆς''' τὴν μήτε πλουσίους ἄγαν μήτε πένητας ἔχουσαν πολίτας.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §11'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§11. Après lui '''Thalès''' : « que c’est celle qui n’a ni des citoyens trop riches, ni des citoyens trop pauvres. »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §11'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe XII.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Réflexion de '''Thalès''' sur la manière dont une maison doit être réglée.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§12. Τοῦτον οὖν ἄριστον ὁ '''Σόλων''' εἶπεν αὑτῷ δοκεῖν οἶκον, ὅπου τὰ χρήματα μήτε κτωμένοις ἀδικία μήτε φυλάττουσιν ἀπιστία μήτε δαπανῶσι μετάνοια πρόσεστιν. Ὁ δὲ '''Βίας''' ἐν ᾧ τοιοῦτός ἐστιν ὁ δεσπότης δι´ αὑτὸν οἷος ἔξω διὰ τὸν νόμον. Ὁ δὲ '''Θαλῆς''' ἐν ᾧ πλείστην ἄγειν τῷ δεσπότῃ σχολὴν ἔξεστιν.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §12'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§12. '''Solon''' déclara que, selon lui, « la meilleure maison est celle où le bien qui s’y trouve est possédé sans injustice, conservé sans défiance, dépensé sans repentir. » '''Bias''' : « celle où, à l’intérieur, le maître est, par respect pour lui-même, ce qu’il est au dehors par respect pour la loi. » '''Thalès''' : « celle où le maître peut avoir un très grand loisir. »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §12'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe XIV.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de l’''espièglerie'' de '''Thalès'''.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§14. [...] '''Ἐπιστήσαντος''' δὲ τοῦ λόγου τὸ συμπόσιον ὁ μὲν '''Θαλῆς''' ἐπισκώπτων εὖ φρονεῖν ἔφη τὸν '''Ἐπιμενίδην''' ὅτι μὴ βούλεται πράγματα ἔχειν ἀλῶν τὰ σιτία καὶ πέττων ἑαυτῷ, καθάπερ '''Πιττακός'''. « ἐγὼ γάρ, » εἶπε, « τῆς ξένης ἤκουον ᾀδούσης πρὸς τὴν μύλην, ἐν [[w:Eresós|''Ἐρέσῳ'']] γενόμενος, ἄλει, μύλα, ἄλει· καὶ γὰρ '''Πιττακὸς''' ἄλει μεγάλας ''Μυτιλάνας'' βασιλεύων. »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §14'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§14. [...] Ces paroles ayant mis en arrêt les convives, '''Thalès''' dit en raillant qu’'''Epiménide''' avait bien raison de ne pas vouloir se donner l’embarras de moudre et de cuire lui-même son manger, comme faisait '''Pittacus''' : « Car je me souviens », ajouta-t-il, « qu’étant à ''Lesbos'', j’entendis mon hôtesse chanter à sa meule : Va ton train, meule, va ton train, puisque '''Pittacus''', le roi de la grande ''Mitylène'', s’occupe bien à moudre. »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §14'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe XV.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage d’une doctrine de '''Thalès''' sur l’importance vitale de l’agriculture.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§15. [...] « Οὐδαμῶς, » ὁ '''Κλεόδωρος''', « ἔμοιγ´, » εἶπεν, « εἰ δεῖ τὸ φαινόμενον εἰπεῖν, καὶ μάλιστα παρακειμένης τραπέζης, ἣν ἀναιροῦσιν αἰρομένης τροφῆς φιλίων θεῶν βωμὸν οὖσαν καὶ ξενίων. ὡς δὲ '''Θαλῆς''' λέγει τῆς γῆς ἀναιρεθείσης σύγχυσιν τὸν ὅλον ἕξειν κόσμον, οὕτως οἴκου διάλυσις ἐστι· συναναιρεῖται γὰρ αὐτῇ πῦρ ἑστιοῦχον ἑστία κρατῆρες ὑποδοχαὶ ξενισμοί, φιλανθρωπότατα καὶ πρῶτα κοινωνήματα πρὸς ἀλλήλους, μᾶλλον δὲ σύμπας ὁ βίος, εἴ γε διαγωγή τίς ἐστιν ἀνθρώπου πράξεων ἔχουσα διέξοδον, ὧν ἡ τῆς τροφῆς χρεία καὶ παρασκευὴ τὰς πλείστας παρακαλεῖ. [...] »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §15'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§15. [...] « S’il faut dire ce que je pense », reprit '''Cléodème''', « ce n’est nullement mon avis, et surtout quand est dressée la table, que l’on supprime si la nourriture est supprimée et qui est l’autel des dieux amis et hospitaliers. Et s’il est vrai, comme dit '''Thalès''', que la suppression de la terre dût entraîner le désordre et la ruine du monde entier, de même anéantir la table ce serait anéantir la maison. Avec la table disparaîtraient le feu qui consacre le foyer, le foyer lui-même, les coupes, les réceptions, les hospitalités, qui sont les plus affectueux et les premiers rapports de communauté entre les hommes; ou plutôt disparaîtrait la vie entière, s’il est vrai que la vie soit une sorte de courant formé par la série des actes de l’homme, actes dont le plus grand nombre est commandé par le besoin et la préparation de la nourriture.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §15'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe XVII.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage d’une parole sage de '''Thalès''' : Ne croire ni ses ennemis sur les choses croyables, ni ses amis sur les choses incroyables.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§17. τέλος δὲ γελάσας πρὸς ἡμᾶς « βούλομαι μέν, » ἔφη, « πρὸς τὸ παρὸν φράσαι τὸ προσηγγελμένον· ὀκνῶ δ´ ἀκούσας '''Θαλέω''' ποτ´ εἰπόντος ὅτι δεῖ τὰ μὲν εἰκότα λέγειν, τὰ δ´ ἀμήχανα σιωπᾶν. » Ὑπολαβὼν οὖν ὁ '''Βίας''' « ἀλλὰ καὶ τοῦτ´, » ἔφη, « '''Θαλέω''' τὸ σοφόν ἐστιν, ὅτι δεῖ τοῖς μὲν ἐχθροῖς καὶ περὶ τῶν πιστῶν ἀπιστεῖν, τοῖς δὲ φίλοις καὶ τὰ ἄπιστα πιστεύειν, ἐχθροὺς μέν, ἔγωγ´ ἡγοῦμαι, τοὺς πονηροὺς καὶ ἀνοήτους, φίλους δὲ τοὺς χρηστοὺς καὶ φρονίμους αὐτοῦ καλοῦντος. οὐκοῦν, »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §17'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§17. [...] A la fin il s’adressa à nous en éclatant de rire : « Je voudrais vous faire connaître, sans plus attendre, ce que '''Gorgias''' vient de me conter; et pourtant j’hésite, parce que j’ai autrefois entendu dire à '''Thalès''' qu’il faut dire les choses vraisemblables et taire les impossibles. « Mais, reprit '''Bias''', « c’est à '''Thalès''' aussi qu’appartient cette sage parole : qu’il faut ne pas croire ses ennemis même sur les choses croyables, et croire ses amis même sur celles qui ne le sont pas : par ennemis il entendait, je suppose, les méchants et les sots, par amis, les gens vertueux et sensés. »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §17'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe XXI.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage d’une doctrine de '''Thalès''' sur la résidence de l'âme dans toutes les parties du monde les plus essentielles.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§21. Μετὰ δὲ τοῦτον ὁ '''Ἀνάχαρσις''' εἶπεν ὅτι τοῦ '''Θαλέω''' καλῶς ὑπολαμβάνοντος ἐν πᾶσιν εἶναι τοῖς κυριωτάτοις μέρεσι τοῦ κόσμου καὶ μεγίστοις ψυχήν, [...].</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §21'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§21. Après '''Pittacus''', '''Anacharsis''' prit la parole : « Puisque, comme '''Thalès''' l’a magnifiquement établi, une âme réside dans toutes les parties du monde les plus essentielles, [...].</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://remacle.org/bloodwolf/historiens/Plutarque/banquet.htm Των επτα σοφων συμποσιον - Le banquet des sept sages], §21'', texte établi par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;"></div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome I, [https://www.google.fr/books/edition/Oeuvres_morales_de_Plutarque/QnRQAAAAcAAJ?hl=fr&gbpv=0 Le banquet des sept sages]'' pp.325, 326, traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">― ✳ ―</div> ==== Du démon de '''Socrate''' ==== <div style="text-align: center; margin: 0 1em;"></div> ===== <div style="text-align: center;">Paragraphe VI.</div> ===== <div style="text-align: center; margin: 0 1em;">Témoignage de l’aversion de '''Thalès''' pour les [[w:Tyran|''tyrans'']].</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid;"><br /><div style="text-align: justify; margin: 0 2em; text-indent: 15px">§6. Ταῦτα τοῦ '''Θεοκρίτου''' λέγοντος ὁ '''Λεοντίδης''' ἐξῄει μετὰ τῶν φίλων, ἡμεῖς δ´ εἰσελθόντες ἠσπαζόμεθα τὸν '''Σιμμίαν''' ἐπὶ τῆς κλίνης καθεζόμενον οὐ κατατετευχότα τῆς δεήσεως, οἶμαι, μάλα σύννουν καὶ διαλελυπημένον· ἀποβλέψας δὲ πρὸς ἅπαντας ἡμᾶς ‘ὦ '''Ἡράκλεις''',’ εἶπεν ‘ἀγρίων καὶ βαρβάρων ἠθῶν· εἶτ´ οὐχ ὑπέρευ '''Θαλῆς''' ὁ παλαιὸς ἀπὸ ξένης ἐλθὼν διὰ χρόνου τῶν φίλων ἐρωτώντων ὅ τι καινότατον ἱστορήκοι ’τύραννον‘ ἔφη ’γέροντα.‘ καὶ γὰρ ᾧ μηδὲν ἰδίᾳ συμβέβηκεν ἀδικεῖσθαι, τὸ βάρος αὐτὸ καὶ τὴν σκληρότητα τῆς ὁμιλίας δυσχεραίνων ἐχθρός ἐστι τῶν ἀνόμων καὶ ἀνυπευθύνων δυναστειῶν.[...]</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome II, [https://remacle.org/bloodwolf/historiens/Plutarque/demonsocrategr.htm Περι του Σωκρατους Δαιμονιου - Le Démon de Socrate], §6'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§6. Pendant que [[w:Théocrite|'''Théocritos''']] [[#Théocrite|<span id="Théocrite_back"><sup>'''I'''</sup></span>]] parlait, '''Léontidas''' sortit avec ses amis, et nous entrâmes chez '''Simmias''', que nous saluâmes affectueusement. Il était assis sur son lit, et j’attribuai à l’insuccès de sa demande son air pensif et affligé. Après nous avoir regardés tous : « Par '''Hercule''' ! s’écria-t-il, quelles mœurs sauvages et barbares ! Eh bien ! n’avait-il pas cent fois raison le '''Thalès''' des anciens jours ? Comme il était revenu d’un long voyage à l’étranger, ses amis lui demandaient ce qu’il avait vu de plus curieux : « Un tyran parvenu à la vieillesse », répondit-il. Car ceux même qui n’ont pas éprouvé de la part d’un despote une injustice personnelle, supportent avec peine le poids et la dureté d’un semblable régime. On déteste tout pouvoir qui est en dehors de la loi et qui ne veut rendre de comptes à personne. [...]</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome III, [[s:Page:Plutarque_-_Œuvres_complètes_de_Plutarque_-_Œuvres_morales_et_œuvres_diverses,_tome_3,_1870.djvu/74|Περι του Σωκρατους Δαιμονιου - Le Démon de Socrate]], [[s:Page:Plutarque_-_Œuvres_complètes_de_Plutarque_-_Œuvres_morales_et_œuvres_diverses,_tome_3,_1870.djvu/83|§6]]'', traduites par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> {{Boîte déroulante début|titre=NdA de trad. Victor Bétolaud de 1870|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Théocrite_back|<span id="Théocrite"><sup>I</sup></span>]] Du nom propre grec ancien Θεόκριτος / Theókritos [[wikt:en:Θεόκριτος#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">➥ de l’adjectif et du nom commun θεός / theós [[wikt:en:θεός#Ancient_Greek|(en)]], « divin, dieu, divinité »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ +‎ du verbe κρίνω / krínō, « 1. (transitif) Séparer, diviser, distinguer entre deux choses ou personnes ou parmi un groupe de choses ou de personnes. 2. (transitif) Commander, organiser. 3. S’enquérir, enquêter. 4. Sélectionner, choisir, préférer. 5. (transitif) Trancher un différend ou un concours ; (intransitif) Porter un jugement, prendre une décision : • (voix moyenne, voix passive) Décider d’un concours ; (voix moyenne et voix passive) Se disputer, se disputer, se quereller. 6. Décider ou juger [+accusatif et infinitif = que quelque chose fait quelque chose], [+accusatif et accusatif = que quelque chose est quelque chose]. 7. Discerner entre le bien et le mal. 8. Juger, prononcer. 9. Traduire en justice, accuser. 10. Condamner, critiquer. 11. Sécréter, cacher, dissimuler. »;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ + le suffixe adjectival récessif‎ -τος / -tos [[wikt:en:-τος#Ancient_Greek|(en)]];<br /><p style="margin: 0 2em; text-indent: 15px;">''Poète'', auteur de [[w:Mime|''mimes'']] (imitations comiques du langage ou des gestes), d’[[w:Poésie_pastorale|''idylles pastorales'']] et de [[w:Épopée|''contes épiques'']].<br /><p style="text-align: right; margin: 0 2em;">([[w:Circa|{{Info|''ca.''|Circa, locution latine que l’on emploie pour indiquer l’approximation d’une date}}]] [[w:310_av._J.-C.|-310]]<sup>[[w:IVe_siècle_av._J.-C.|⏳]]</sup>, à [[w:Théocrite#cite_ref-2|''Syracuse'']] — {{Info|''ca.''|Circa, locution latine que l’on emploie pour indiquer l’approximation d’une date}} [[w:250_av._J.-C.|-250]]<sup>[[w:IIIe_siècle_av._J.-C.|⏳]]</sup>)'''<br /><br /> </div>{{Boîte déroulante fin}} <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">§6. Pendant que '''Théocritos''' discourait ainsi, '''Léontidas''' sortit avec ses amis ; nous entrâmes et saluâmes '''Simmias''', qui était assis sur son lit, tout soucieux et triste, parce que sa demande avait été rejetée sans doute. En nous regardant tous, il s’écria : « '''Héraclès''' ! les moeurs sauvages et barbares ! Ah ! que '''Thalès''' l’ancien avait raison de répondre, lorsqu’après un long voyage à l’étranger ses amis lui demandaient ce qu’il avait remarqué de plus extraordinaire : « Un tyran âgé ». Même un homme qui a eu la chance de n’être pour son compte victime d’aucune injustice exècre déjà le poids et la dureté de ce commerce et est ennemi des dictatures, des dominations arbitraires.[...]</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome II, [https://remacle.org/bloodwolf/historiens/Plutarque/demonsocrate.htm Περι του Σωκρατους Δαιμονιου - Le Démon de Socrate], §6'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">― ✳ ―</div> ==== Les [[w:Alimentation_en_Grèce_antique#Banquets|''Symposiaques'']] [[#Symposiaques|<span id="Symposiaques_back"><sup>'''I'''</sup></span>]], ou questions de table ==== <div style="text-align: center; margin: 0 1em;"></div> {{Boîte déroulante début|titre=NdA Symposiaques|fondtitre=#ffffff|alignT=right|styleTitre=color:#3366BB;|styleFrame=border-color:white}} <div style="text-align: justify; border: 2px solid #3366BB; text-indent: 15px; border-radius:15px; font-size:85%;"> <p style="margin: 0 2em; text-indent: 15px;"><br/>'''[[#Symposiaques_back|<span id="Symposiaques"><sup>I</sup></span>]] Du nom commun grec ancien συμπόσιον / sympósion [[wikt:en:συμπόσιον#Ancient_Greek|(en)]], « Seconde partie d’un repas pendant laquelle un groupe restreint de convives buvaient et discutaient sur un sujet. » ; <br /><p style="margin: 0 2em; text-indent: 15px;">➥ du nom commun σῠμπότης / sŭmpótēs [[wikt:en:συμπότης#Ancient_Greek|(en)]], « compagnon de boisson » ; <br /><p style="margin: 0 4em; text-indent: 15px;">➥ du préfixe σῠν- / sŭn- [[wikt:en:συν-#Ancient_Greek|(en)]], « avec, ensemble » ;<br /><p style="margin: 0 4em; text-indent: 15px;">➥ +‎ du nom commun ποτής / potḗs [[wikt:en:ποτής#Ancient_Greek|(en)]], « boisson » ;<br /><p style="margin: 0 2em; text-indent: 15px;">➥ +‎ du suffixe -ῐον / -ĭon [[wikt:en:-ιον#Ancient_Greek|(en)]] ;<br /><p style="margin: 0 2em; text-indent: 15px;">Entretiens dans un banquet, propos de table. '''<br /><br /> </div>{{Boîte déroulante fin}} ===== <div style="text-align: center;">Livre III</div> ===== <div style="text-align: center; margin: 0 1em;"></div> ====== <div style="text-align: center;">Question VI. Sur le temps où il est à propos de se rapprocher d’une femme.</div> ====== <div style="text-align: center; margin: 0 1em;">Témoignage du statut marital de '''Thalès''' et de sa relation avec sa mère</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid;"><br /><div style="text-align: justify; margin: 0 2em; text-indent: 15px">Καθάπερ οὖν '''Θαλῆς''' ὁ σοφὸς ὑπὸ τῆς μητρὸς ἐνοχλούμενος γῆμαι κελευούσης πῶς ὑπεξέφυγε παρήγαγε λέγων πρὸς αὐτὴν ἐν ἀρχῇ μέν « οὔπω καιρὸς ὦ μῆτερ, » ὕστερον δ´ « οὐκέτι καιρὸς ὦ μῆτερ », οὕτως ἄρα καὶ πρὸς ἀφροδίσια κράτιστον {ἔσται} ἔχειν ἕκαστον, ὥστε κατακλινόμενον λέγειν «οὔπω καιρός», ἀνιστάμενον δ´ « οὐκέτι καιρός ».</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome III, [https://remacle.org/bloodwolf/historiens/Plutarque/sympos3.htm#VI Προβλημα Ϛ. Περὶ καιροῦ συνουσίας. - Question VI. Quel est le temps le plus propre à l'amour?]'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div></div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§3. Ainsi donc, comme le sage '''Thalès''', fatigué par sa mère qui l’engageait à se marier, trouva le moyen de lui échapper et de lui donner le change en lui disant une première fois : « Il n’est pas encore temps, ma mère », et, lorsqu’elle insistait encore après qu’il avait passé l’âge : « Il n’est plus temps » ; de même, pour ce qui regarde les plaisirs de l’amour, le mieux sera que chacun se détermine à dire, en se mettant au lit : « Il n’est pas temps encore », et, en sortant du lit : « il n’est plus temps ».</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome III, [[s:Page:Plutarque_-_Œuvres_complètes_de_Plutarque_-_Œuvres_morales_et_œuvres_diverses,_tome_3,_1870.djvu/268|Question VI. Sur le temps où il est à propos de se rapprocher d’une femme]], [[s:Page:Plutarque_-_Œuvres_complètes_de_Plutarque_-_Œuvres_morales_et_œuvres_diverses,_tome_3,_1870.djvu/271|§3]]'', traduites par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">Le sage '''Thalès''', pressé par sa mère de se marier, lui répondit avec beaucoup d’adresse. Au commencement, il lui dit : Ma mère, il n’est pas encore temps. Quand il eut passé la fleur de son âge, et qu’elle lui fit de nouvelles instances, il lui répondit : Il n’est plus temps. De même, par rapport à la question proposée, le mieux serait que chacun pût se dire le soir en se couchant : Il n'm’est pas encore temps ; et le matin, quand il se lève : Il n’est plus temps. »</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome III, [https://remacle.org/bloodwolf/historiens/Plutarque/sympos3.htm#VI Προβλημα Ϛ. Περὶ καιροῦ συνουσίας. - Question VI. Quel est le temps le plus propre à l'amour?]'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">― ✳ ―</div> ==== De la malignité d’'''Hérodote''' ==== <div style="text-align: center; margin: 0 1em;"></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">― ✳ ―</div> ==== Les opinions des Philosophes ==== <div style="text-align: center; margin: 0 1em;"></div> ===== <div style="text-align: center;">Livre I</div> ===== <div style="text-align: center; margin: 0 1em;"></div> <div style="text-align: center; margin: 0 auto;">― ● ―</div> ===== <div style="text-align: center;">Livre II</div> ===== <div style="text-align: center; margin: 0 1em;"></div> <div style="text-align: center; margin: 0 auto;">― ● ―</div> ===== <div style="text-align: center;">Livre III</div> ===== <div style="text-align: center; margin: 0 1em;"></div> <div style="text-align: center; margin: 0 auto;">― ● ―</div> ===== <div style="text-align: center;">Livre IV</div> ===== <div style="text-align: center; margin: 0 1em;"></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">― ✳ ―</div> ==== Les animaux de terre ont-ils plus d'adresse que ceux de mer? ==== <div style="text-align: center; margin: 0 1em;"></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">― ✳ ―</div> ==== D’'''Isis''' et d’'''Osiris'''==== <div style="text-align: center; margin: 0 1em;"></div> ===== <div style="text-align: center;">Paragraphe IX.</div> ===== <div style="text-align: center; margin: 0 1em;">Témoignage d’un voyage de '''Thalès''' en ''Égypte'', de rencontres avec des prêtres et de récit sur leur divinités.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid;"><br /><div style="text-align: justify; margin: 0 2em; text-indent: 15px">§9. [...] '''Ἑκαταῖος''' δ´ ὁ ''Ἀβδηρίτης'' φησὶ τούτῳ καὶ πρὸς ἀλλήλους τῷ ῥήματι χρῆσθαι τοὺς ''Αἰγυπτίους'', ὅταν τινὰ προσκαλῶνται· προσκλητικὴν γὰρ εἶναι τὴν φωνήν. Διὸ τὸν πρῶτον θεόν, ὃν τῷ παντὶ τὸν αὐτὸν νομίζουσιν, ὡς ἀφανῆ καὶ κεκρυμμένον ὄντα προσκαλούμενοι καὶ παρακαλοῦντες ἐμφανῆ γενέσθαι καὶ δῆλον αὐτοῖς ''Ἀμοῦν'' λέγουσιν.<br /><p style="text-indent: 15px">§10. Ἡ μὲν οὖν εὐλάβεια τῆς περὶ τὰ θεῖα σοφίας '''Αἰγυπτίων''' τοσαύτη {ἦν}, μαρτυροῦσι δὲ καὶ τῶν ''Ἑλλήνων'' οἱ σοφώτατοι, '''Σόλων''' '''Θαλῆς''' '''Πλάτων''' '''Εὔδοξος''' '''Πυθαγόρας''', ὡς δ´ ἔνιοί φασι, καὶ '''Λυκοῦργος''' εἰς ''Αἴγυπτον'' ἀφικόμενοι καὶ συγγενόμενοι τοῖς ἱερεῦσιν.[...]</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome V, [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris.htm Περι Ισιδος και Οσιριδος - Traité d’Isis et d’Osiris], §34'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1870<br />(également disponible une édition de 1844 [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris1.htm ici])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§9. [...] '''Hécatée''' d’''Abdère'' dit que les ''Égyptiens'' emploient ce mot pour s’appeler les uns les autres, attendu qu’il est essentiellement appellatif. C’est pourquoi, s’adressant au premier Dieu, le même, selon eux, que l’Univers, comme à un être invisible et caché, ils l’exhortent avec supplications, en l’appelant "Amoun", à se faire voir et à se découvrir à eux. Voilà jusqu’à quel point était grande la réserve qui caractérisait la philosophie religieuse des ''Égyptiens''.<br /><p style="text-indent: 15px">§10. C’est ce que témoignent les plus éclairés d’entre les ''Grecs'': '''Solon''', '''Thalès''', '''Platon''', '''Eudoxe''', '''Pythagore''', et aussi, d’après quelques-uns, '''Lycurgue'''. Ils étaient allés en ''Égypte'' et avaient eu des conférences avec les prêtres. [...]</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome V, [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris.htm Περι Ισιδος και Οσιριδος - Traité d’Isis et d’Osiris], §§9, 10'', traduites par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870<br/>(également disponible [[s:Sur_Isis_et_Osiris|ici]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">§9. [...] '''Hécatée''' d’''Abdère'' dit que les ''Egyptiens'' s’en servent pour s’appeler les uns les autres; que ce nom est de sa nature appellatif ; que ce peuple , qui croit que le premier des dieux, qu’il confond avec l’univers, est un dieu caché et inconnu, l’invoque et le prie de se découvrir à eux, en lui disant ''Amoun'' ;<br /><p style="text-indent: 15px">§10. tant ce peuple portait de retenue et de réserve dans sa philosophie religieuse ! C’est ce qu'attestent unanimement les plus sages d’entre les ''Grecs'', '''Solon''', '''Thalès''', '''Platon''', '''Eudoxe''', '''Pythagore''', et, suivant quelques uns, '''Lycurgue''' lui-même, qui tous voyagèrent en ''Egypte'', et y conférèrent avec les prêtres du pays.[...]</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome V, [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris1.htm Περι Ισιδος και Οσιριδος - Traité d’Isis et d’Osiris], §§9, 10'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto;">― • ―</div> ===== <div style="text-align: center;">Paragraphe XXXIV.</div> ===== <div style="text-align: justify; text-indent: 15px; margin: 0 1em;">Témoignage de la théorie de '''Thalès''' de l’eau principe de tous les êtres.</div> :'''Texte grec''' <div style="text-align: justify; margin-top: 2em; overflow: auto; height: 270px; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§34. Ἥλιον δὲ καὶ Σελήνην οὐχ ἅρμασιν ἀλλὰ πλοίοις ὀχήμασι χρωμένους περιπολεῖν φασιν αἰνιττόμενοι τὴν ἀφ´ ὑγροῦ τροφὴν αὐτῶν καὶ γένεσιν. Οἴονται δὲ καὶ '''Ὅμηρον''' ὥσπερ '''Θαλῆν''' μαθόντα παρ´ ''Αἰγυπτίων'' ὕδωρ ἀρχὴν ἁπάντων καὶ γένεσιν τίθεσθαι· τὸν γὰρ '''Ὠκεανὸν''' '''Ὄσιριν''' εἶναι, τὴν δὲ '''Τηθὺν''' '''Ἶσιν''' ὡς τιθηνουμένην πάντα καὶ συνεκτρέφουσαν. [...]</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome V, [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris.htm Περι Ισιδος και Οσιριδος - Traité d’Isis et d’Osiris], §34'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1870<br />(également disponible une édition de 1844 [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris1.htm ici])</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> :'''Traductions''' <div style="text-align: justify; margin-top: 2em; height: 520px; overflow: auto; border: 2px solid; text-indent: 15px"><br /><div style="text-align: justify; margin: 0 2em;">§34. Ils disent que le soleil et la lune parcourent leur route perpétuelle non pas sur des chars, mais sur des bâtiments de navigation: signifiant par là, que c’est le principe humide qui les entretient et qui leur a donné naissance. Ils croient aussi que c’est des ''Egyptiens'' qu’'''Homère''', et après lui '''Thalès''', ont appris à établir l’eau comme principe générateur de tous les êtres. Ils veulent qu’'''Osiris''' soit l’'''Océan''', qu’'''Isis''' soit '''Téthys''', laquelle nourrit et entretient tout ce qui existe.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome V, [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris1.htm Περι Ισιδος και Οσιριδος - Traité d’Isis et d’Osiris], §34'', traduites par [[w:Victor_Bétolaud|Victor Bétolaud]], 1870<br/>(également disponible [[s:Sur_Isis_et_Osiris|ici]])</div> <div class="center" style="border-bottom: 1px solid #444; width: 30%; margin: 0 auto;"></div> <div style="text-indent: 15px; text-align: justify; margin: 0 2em;">§34. Ils disent que le soleil et la lune parcourent les cieux, portés, non sur des chars, mais sur des vaisseaux, pour signifier que tout est nourri et mis en mouvement par l’eau. Ils pensent que c’est des ''Egyptiens'' qu’'''Homère''' et '''Thalès''' avaient pris cette opinion, que l’eau est le principe de tous les êtres, qu’'''Osiris''' est l’'''Océan''', et qu’'''Isis''' est '''Thétis''', qui nourrit et alimente toutes les substances.</div> <div style="text-align: right; margin: 0 2em 0 1em;"><u>Œuvres complètes de Plutarque, Œuvres Morales</u>, ''Tome V, [https://remacle.org/bloodwolf/historiens/Plutarque/isisetosiris1.htm Περι Ισιδος και Οσιριδος - Traité d’Isis et d’Osiris], §34'', traduites par [[w:Dominique_Ricard|Dominique Ricard]], 1844</div> <div class="center" style="border-bottom: 1px solid #FFF; width: 30%; margin: 0 auto;"></div></div> <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Valère_Maxime|'''Valère Maxime''']] ''([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l'on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:Ier_siècle|I<sup>er</sup> siècle {{Info|EC|de l’Ère Commune}}]])'' [[s:Auteur:Valère_Maxime|<sup>📚</sup>]] == === Actions et paroles mémorables, VII, § 2 === :8. Il y a aussi un mot admirable de Thalès. On lui demandait si les actions des hommes échappaient à la connaissance des dieux. "Leurs pensées non plus", répondit-il. Aussi faut-il nous appliquer à avoir, je ne dis pas seulement les mains, mais encore le cœur pur, dans la persuasion que la divinité est témoin des mouvements les plus secrets de nos âmes. <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Tatien_le_Syrien|'''Tatien''']] le [[w:Assyrie_(province_romaine)|''Syrien'']] ''(vers [[w:120|120]], en [[w:Halicarnasse|''Halicarnasse'']] en [[w:Assyrie_(province_romaine)|Assyrie]] — vers [[w:173|173]], en Assyrie)'' <sup>[[w:IIe_siècle|⏳]]</sup> [[s:Auteur:Tatien_le_Syrien|<sup>📚</sup>]] == === Discours aux Grecs, 41 === = [[w:Lucien_de_Samosate|'''Lucien de Samosate''']] ''(vers [[w:120|120]], à [[w:Samosate|Samosate]] — vers [[w:180|180]], en [[w:Égypte_romaine_et_byzantine|Égypte]])'' <sup>[[w:IIe_siècle|⏳]]</sup> [[s:Auteur:Lucien_de_Samosate|<sup>📚</sup>]] = === Dialogues des morts === https://gallica.bnf.fr/ark:/12148/bpt6k6227866x/f7.item https://gallica.bnf.fr/ark:/12148/bpt6k6227866x/f103.double === Hippias ou le bain === :(2) Mon but est de prouver que les constructeurs de machines qui méritent le plus notre admiration sont ceux qui, distingués par leur science théorique, ont laissé en outre à la postérité des monuments de leur art et des œuvres de leur génie, tandis que les hommes, qui se sont seulement exercés dans la parole méritent plutôt le nom de sophistes que celui de savants. C'est sur la liste traditionnelle de ces artistes que nous voyons figurer Archimède et Socrate de Cnide, qui inventèrent, l'un les moyens de soumettre à Ptolémée la ville de Memphis, sans recourir à un siège, mais en détournant et en divisant le cours du Nil ; l'autre, ceux d'incendier les galères des ennemis. Avant eux, Thalès de Milet, ayant promis à Crésus de faire passer à pied sec à son armée les eaux du fleuve Halys, imagina de les détourner en une seule nuit derrière le camp ; et pourtant ce n'était pas un mécanicien de profession, mais un sage d'un esprit inventif et à l'intelligence duquel on pouvait s'en rapporter. === Exemples de longévité === :(18) Solon, Thalès et Pittacus, que l'on compte au nombre des Sept sages, vécurent chacun cent années. <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Clément_d%27Alexandrie|'''Clément''']] d'[[w:Histoire_d%27Alexandrie#Annexion|''Alexandrie'']] ''(vers [[w:150|150]]'' <sup>[[w:IIe_siècle|⏳]]</sup>'', à [[w:Athènes#Antiquité|Athènes]] — vers [[w:215|215]]'' <sup>[[w:IIIe_siècle|⏳]]</sup>'', à [[w:Kayseri|Kayseri]])'' [[s:Auteur:Clément_d’Alexandrie|<sup>📚</sup>]] == === Stromates, I, 65 === <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Claude_Élien|'''Claude Élien''']] ''(vers [[w:175|175]]''<sup>[[w:IIe_siècle|⏳]]</sup>'', à [[w:Préneste|Préneste]] — vers [[w:235|235]]''<sup>[[w:IIIe_siècle|⏳]]</sup>'', en [[w:Rome_antique|Rome]])'' [[s:Auteur:Élien_le_sophiste|<sup>📚</sup>]] == === Histoires diverses === :On a vu des philosophes à la tête des affaires publiques : d'autres, se bornant à cultiver leur raison, ont passé leur vie dans le repos. Entre les premiers sont Zaleucus et Charondas qui réformèrent, l'un, le gouvernement des Locriens, l'autre, d'abord celui des Catanéens, puis, après qu'il eut été exilé de Catane, celui des Rhéginiens. Archytas servit utilement les Tarentins. Les Athéniens durent tout à Solon. Bias et Thalès rendirent les mêmes services à l'Ionie, Chilon à Lacédémone, Pittacus à Mitylène, Cléobule à Rhodes. <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Sextus_Empiricus|'''Sextus Empiricus''']] ''([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l'on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:IIe_siècle|II<sup>ème</sup> siècle {{Info|EC|de l’Ère Commune}}]])'' [[s:Auteur:Sextus_Empiricus|<sup>📚</sup>]] == Hypot III, 30, et Liv I contre les phys., sect. 319 https://gallica.bnf.fr/ark:/12148/bpt6k9796311p/f305.item.r=thales <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Lactance|'''Lactance''']] ''(vers [[w:250|250]]''<sup>[[w:IIIe_siècle|⏳]]</sup>'', à [[w:Henchir_Kssiba#Histoire|Civitas Popthensis]] — vers [[w:325|325]]''<sup>[[w:IVe_siècle|⏳]]</sup>'', en [[w:Gaule#La_Gaule_dans_l'Antiquité_tardive|Gaule]])'' == Épit. 4 (https://gallica.bnf.fr/ark:/12148/bpt6k282068z/f300.image.r=thales) Inst. 111, 16 (https://gallica.bnf.fr/ark:/12148/bpt6k282068z/f621.item.r=thales) <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Jamblique|'''Jamblique''']] ''(vers [[w:250|250]]''<sup>[[w:IIIe_siècle|⏳]]</sup>'', à [[w:Qinnasrīn|Chalcis ad Belum]] — vers [[w:333|333]]''<sup>[[w:IVe_siècle|⏳]]</sup>'')'' == === Vie de Pythagore === ==== chap. II ==== (https://web.archive.org/web/20110721184914/http://www.aurumsolis.info/index.php?option=com_phocadownload&view=category&download=1%3Aiamblichus-the-pythagorean-life&id=19%3Awritings-from-the-founders&Itemid=143&lang=en) <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Eusèbe_de_Césarée|'''Eusèbe''']] de [[w:Césarée#Césarée_au_début_du_christianisme|''Césarée'']] ''(vers [[w:265|265]]'' <sup>[[w:IIIe_siècle|⏳]]</sup>'', à Césarée — [[w:340|340]]'' <sup>[[w:IVe_siècle|⏳]]</sup>'', à Césarée)'' [[s:Auteur:Eusèbe_de_Césarée|<sup>📚</sup>]] == Preparation Évangélique, XI, 2 Prepar. évang. I, 8, page 22-25 https://gallica.bnf.fr/ark:/12148/bpt6k9796311p/f310.image.r=thales <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Diogène_Laërce|'''Diogène Laërce''']] ''([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l'on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] [[w:IIIe_siècle|III<sup>ème</sup> siècle {{Info|EC|de l’Ère Commune}}]])'' [[s:Auteur:Diogène_Laërce|<sup>📚</sup>]] == ''Ce texte est un extrait de la traduction de Robert Genaille (1933)'' Thalès[1], au dire d’Hérodote, de Douris et de Démocrite, était fils d’Examios et de Cléobuline, et membre de la famille des Thélides, Phéniciens descendant en droite ligne d’Agénor[2] et de Cadmus[3], s’il faut en croire Platon. Le premier, il porta le nom de sage, au temps où Damasias était archonte à Athènes[4]. C’est sous le même archontat que fut créée l’expression : « les sept sages » (cf. Démocrite de Phalère, Registre des Archontes). Thalès fut inscrit comme citoyen de Milet quand il vint dans cette ville avec Nélée chassé de Phénicie. Une autre tradition très courante veut qu’il soit natif de Milet et qu’il descende d’une bonne famille. Il s’occupa de politique avant d’étudier la nature. On croit qu’il ne laissa aucun écrit, car l’Astrologie nautique qu’on lui attribue est de Phocos de Samos. Callimaque[5] croit qu’il découvrit la Petite Ourse et le raconte en vers iambiques : Il mesura, dit-on, les étoiles du Chariot Sur quoi les Phéniciens règlent leur navigation. D’autres auteurs disent qu’il écrivit seulement deux ouvrages, un sur le solstice et un sur l’équinoxe, car il pensait le reste inaccessible. Il passe pour avoir le premier étudié l’astrologie et prédit les éclipses de soleil et les solstices (cf. Eudème, Histoire de l’astrologie)[6]. Xénophane et Hérodote le louent à ce propos, et leur témoignage est confirmé par celui d’Héraclite et de Démocrite. On dit encore (cf. le poète Choirilos) qu’il fut le premier à affirmer l’immortalité des âmes. Le premier il dessina la course du soleil d’un solstice à l’autre, et démontra que comparée au soleil, la lune en est la cent vingtième partie. C’est encore lui qui fixa à trente jours la durée du mois, et qui écrivit le premier traité sur la Nature. Aristote et Hippias disent aussi qu’il accordait une âme aux choses qu’on croit inanimées ; il en donnait pour preuve l’ambre et la pierre de Magnésie. Selon Pamphile[7], il apprit des Égyptiens la géométrie, inscrivit dans un cercle le triangle rectangle, et pour cette découverte immola un bœuf. D’autres, comme Apollodore le calculateur, attribuent cette invention à Pythagore. Thalès a encore développé et précisé l’invention du Phrygien Euphorbe citée par Callimaque dans ses Iambes et concernant le triangle scalène, et tout ce qui touche aux considérations sur les lignes. Il semble encore avoir été en politique un homme de bon conseil. Ainsi, quand Crésus[8] envoya une ambassade aux Milésiens pour demander leur alliance, il s’y opposa, et son intervention sauva la ville, puisque Cyrus l’emporta. Héraclite cite une opinion de Clytos selon laquelle Thalès aurait eu une vie retirée et solitaire. Les uns disent qu’il se maria et eut un fils nommé Kibissos. D’autres prétendent qu’il resta célibataire et adopta le fils de sa sœur, qu’on lui demanda un jour pourquoi il ne cherchait pas à avoir des enfants, et qu’il répondit : « Par amour pour les enfants. » Sa mère l’exhortait à se marier, il lui répondit : « Non, par Zeus, il n’est pas encore temps. » Elle l’y invita une nouvelle fois quand il eut pris de l’âge, mais il lui dit : « Il n’est plus temps. » D’après Hiéronyme de Rhodes (Notes, livre II), il voulut montrer combien il était facile de s’enrichir ayant prévu pour l’année une abondante récolte d’huile, il prit à loyer une oliveraie et gagna beaucoup d’argent[9]. Il soupçonna que l’eau était le principe des choses, que le monde était animé et rempli de démons. On dit qu’il découvrit les saisons de l’année, et qu’il la divisa en trois cent soixante-cinq jours. Il ne suivit les leçons d’aucun maître, sauf en Égypte, où il fréquenta les prêtres du pays. A ce propos, Hiéronyme dit qu’il mesura les Pyramides en calculant le rapport entre leur ombre et celle de notre corps. Si l’on en croit Minuès, il vivait au temps de Thrasybule, qui fut tyran de Milet[10]. L’histoire du trépied trouvé par des pêcheurs et dédié aux sages par le peuple de Milet est bien connue. Des jeunes gens d’Ionie achetèrent à des pêcheurs milésiens leur coup de filet. Ils tirèrent de l’eau un trépied. On se querella et les Milésiens envoyèrent une ambassade à Delphes. Voici quel fut l’oracle de la divinité : Race de Milet, tu interroges Phébus au sujet d’un trépied ? Au plus sage de tous, je donne ce trépied[11]. Ils le donnent alors à Thalès, qui le donne à un autre, et cet autre à un autre, et ainsi de suite jusqu’à Solon, qui, déclarant que seul le dieu était le plus sage de tous, rendit le trépied à Delphes. Callimaque, dans ses Iambes, rapporte cette histoire autrement ; il la tient de Léandre de Milet. Il dit qu’un certain Bathyclès d’Arcadie laissa en mourant une coupe pour qu’elle fût donnée à l’homme le plus sage. Elle fut donc donnée à Thalès, et après être passée de main en main et avoir fait le tour des sages, elle revint à Thalès. Celui-ci en fit don alors à Apollon de Didyme, en ces termes selon le poème de Callimaque : Thalès me donne au protecteur du peuple du Nil, Thalès qui a reçu deux fois ce présent, ce qui, en prose, se dit ainsi : « Thalès de Milet, fils d’Examios, à Apollon delphien, ce présent qu’il a reçu deux fois des Grecs. » Celui qui portait la coupe de sage en sage, le fils de Bathyclès, s’appelait Thyrion (cf. Éleusis, Livre sur Achille, et Alexon de Mynde, Fables, livre IX). Eudoxe de Cnide et Évanthès de Milet disent de leur côté qu’un ami de Crésus reçut du roi un vase d’or, pour le donner au plus sage des Grecs, qu’il le donna à Thalès et que ce vase parvint jusqu’à Chilon. Celui-ci consulta la Pythie, pour savoir qui était plus sage que lui. Elle répondit que c’était Myson (je parlerai de lui : Eudoxe le met parmi les sages à la place de Cléobule et Platon à la place de Périandre.) Voici la réponse que lui fit la Pythie : Il y a un habitant de l’Oeta, Myson, né à Chénée, Qui plus que toi est riche de sages pensées. L’homme qui consulta l’oracle pour Chilon s’appelait Anacharsis. Dédale le Platonicien et Cléarque disent que la coupe fut envoyée par Crésus à Pittacos, et que c’est ainsi qu’elle passa de main en main. D’après Andron, d’autre part (Livre du trépied), les Argiens décidèrent que le trépied serait attribué comme prix de vertu au plus sage des Grecs. Aristodème de Sparte fut choisi et c’est lui qui donna le trépied à Chilon. Alcée est aussi partisan d’Aristodème dont il parle dans les vers suivants : :Comme jadis Aristodème, dit-on, :Prononça à Sparte cette parole bien juste : :C’est de l’argent, un homme, oui de l’argent, :Car l’homme vertueux n’est jamais pauvre. D’autres disent encore que Périandre envoya à Thrasybule, tyran de Milet, un navire chargé, que ce navire fit naufrage dans la mer de Cos, et que quelque temps après le trépied fut trouvé par des pêcheurs. Phanodicos dit que le trépied fut trouvé dans la mer Attique, porté à la ville, et que l’assemblée du peuple s’étant réunie le fit porter à Bias. Pourquoi cela, je le dirai quand je parlerai de Bias. Selon d’autres auteurs, le trépied avait été fabriqué par Héphaïstos et donné en présent de la part de ce dieu à Pélops lors de son mariage. Il vint ensuite à Ménélas, fut enlevé avec Hélène par Alexandre, jeté dans la mer de Cos à l’instigation de la Spartiate qui prévoyait qu’il serait un sujet de querelle. Plus tard, en ce lieu, des Lébédiens achetèrent le produit d’un coup de filet et c’est le trépied qui fut tiré de l’eau. Il y eut querelle avec les pêcheurs, on vint jusqu’à Cos, et comme on ne s’accordait pas, on s’adressa à Milet, qui était la capitale. Les Milésiens envoyèrent des députés qui ne furent pas écoutés, aussi firent-ils la guerre aux gens de Cos. Comme de chaque côté il mourait beaucoup de gens, l’oracle déclara qu’il fallait donner le trépied au plus sage. Les deux camps s’entendirent alors pour l’attribuer à Thalès, qui par la suite le consacra à Apollon de Didyme. Pour en revenir à la réponse de l’oracle aux gens de Cos, elle disait ceci : La querelle entre Ioniens et Méropes ne cessera pas Avant que le trépied d’or qu’Héphaïstos jeta dans la mer N’ait quitté votre ville pour la maison de l’homme Qui connaît le présent, l’avenir et le passé. La réponse aux Milésiens fut la suivante : Race de Milet, tu interroges Phoebus au sujet d’un trépied... comme il a été dit plus haut. En voilà assez sur ce sujet[12]. Hermippe, dans ses Vies, rapporte à Thalès ce qui est dit par d’autres de Socrate : il aimait à dire qu’il remerciait la fortune de trois choses : d’être un humain et non une bête, d’être un homme et non une femme, enfin d’être un Grec, et non un barbare. On raconte encore qu’étant sorti de chez lui pour contempler les astres, il tomba dans un puits[13]. Une vieille femme survenant se moqua de lui en ces mots : « Comment, Thalès, toi qui n’es pas capable de voir ce qui est à tes pieds, t’imagines-tu pouvoir connaître ce qui est dans le ciel ? » Timon[14] a bien connu aussi la science de Thalès en astronomie, et dans ses Silles, il le loue en ces termes : Comme Thalès, un des sept sages, qui fut savant astronome. L’Argien Lobon dit que ses écrits font un total de quelque deux cents vers, et que sous sa statue on écrivit : Thalès de Milet repose ici dans le sol qui l’a nourri, Il fut un sage, et le premier des astrologues. Voici un de ses poèmes : Le trop parler n’est pas marque d’esprit. Trouvez une seule chose sage, Choisissez une seule chose belle, Et vous clouerez le bec à bien des bavards. On lui attribue encore les sentences suivantes : de tous les êtres, le plus ancien, c’est Dieu, car il n’a pas été engendré ; le plus beau, c’est le monde, car il est l’ouvrage du dieu ; le plus grand, c’est l’espace, car il contient tout ; le plus rapide, c’est l’esprit, car il court partout ; le plus fort, c’est la nécessité, car elle vient à bout de tout ; le plus sage, c’est le temps, parce qu’il découvre tout. La mort, dit-il, ne diffère en rien de la vie. On lui répond : « Pourquoi, alors, ne te donnes-tu pas la mort ? » ; « Parce que vie ou mort, c’est tout un », réplique-t-il. Quelqu’un lui demande ce qui du jour ou de la nuit fut créé d’abord ; il répond : « La nuit est en avance d’un jour. » On lui demande si les mauvaises actions d’un homme échappent au regard des dieux. Il répond : « Ils voient même les mauvaises pensées. » Un homme adultère lui demandait s’il pouvait jurer qu’il n’avait pas commis d’adultère. Il répondit : « Le parjure n’est pas pire que l’adultère. » On lui demandait ce qui était difficile : « Se connaître » dit-il ; ce qui était facile : donner un conseil à autrui ; ce qui était le plus doux : jouir ; ce que c’était que la divinité : un être sans commencement ni fin ; encore une chose difficile : voir un tyran âgé ; comment supporter aisément l’infortune : en voyant ses ennemis plus malheureux encore ; comment vivre vertueusement : en ne faisant pas ce que nous reprochons à autrui ; qui est heureux : l’homme bien portant, riche, courageux et instruit. Il disait encore que l’on doit penser à ses amis aussi bien en leur absence qu’en leur présence, que la beauté ne vient pas d’un beau visage, mais de belles actions. « Ne t’enrichis pas injustement, conseillait-il, et veille à ne pas être cité en justice pour de mauvaises paroles contre tes proches et tes amis. Comme tu traites tes parents, tes enfants te traiteront. » Du Nil[15] il disait qu’il débordait quand ses eaux étaient repoussées par les vents étésiens qui soufflent contre son cours. Apollodore dans ses Chroniques dit que Thalès naquit la première année de la trente-cinquième olympiade[16]. Il mourut dans sa soixante-dix-huitième année ou, comme le dit Sosicrate, dans sa quatre-vingt-dixième année, car ce fut dans la cinquante-huitième olympiade. Il vécut du temps de Crésus, auquel il promit de faire traverser l’Hallys[17] sans pont, en détournant le cours du fleuve. Il y eut cinq autres personnages du nom de Thalès (cf. Démétrios de Magnésie, Homonymes) : un rhéteur de Callatie, au style prétentieux, un peintre de Sicyone, de noble origine, un troisième, très ancien, du temps d’Hésiode, d’Homère et de Lycurgue, un quatrième, mentionné par Douris dans son traité de la peinture, un cinquième, plus jeune et peu connu, cité par Denys dans ses Critiques. Pour en revenir à notre sage, il mourut en regardant les jeux gymniques, pour avoir eu trop chaud et trop soif et par suite de sa fatigue et de son grand âge. Voici son épitaphe : :Ce tombeau, certes, est bien petit, :Mais la renommée de l’homme est allée au ciel. :C’est celui de Thalès le très sage. J’ai écrit sur lui les vers suivants dans le premier livre de mes épigrammes ou « vers de mètres divers[18] : :Tandis qu’il regardait les jeux, ô Zeus Hélios, :Tu as ravi du stade le sage Thalès. :Je te loue de l’avoir rapproché du ciel. Il était si vieux :Que de la terre il ne pouvait plus voir les astres. Thalès est l’auteur du fameux « connais-toi toi-même » qu’Antisthène (Livre des Filiations) attribue à Phémonoé, en déclarant que Chilon se l’appropria mensongèrement. Sur les sept sages, qu’il est juste de citer maintenant l’un après l’autre, voici la tradition. Damon de Cyrène, qui blâme tous les philosophes dans ses écrits, s’attaque surtout aux sept sages. Anaximène dit que tous étaient poètes. Dicéarque dit qu’ils n’étaient ni sages ni philosophes, mais hommes d’esprit et législateurs. Archétimos de Syracuse a décrit leurs assemblées chez Cypsélos[19] et dit qu’il y assista personnellement. Euphoros dit que tous, sauf Thalès, fréquentèrent Crésus. D’autres disent qu’ils se réunirent à Panionium, à Corinthe et à Delphes. On rapporte même leurs paroles, et qui a prononcé telle ou telle. Exemple : Le Spartiate Chilon fut sage, Lui qui dit : Rien de trop, Tout est bien qui vient en son temps ! On n’est pas d’accord sur leur nombre. Léandre, au lieu de Cléobule et de Myson, met Léophante, fils de Gorsias, ou Lébédios d’Éphèse et Épiménide de Crète. Platon, dans le Protagoras[20], met Myson à la place de Périandre. Éphoros met Anacharsis à la place de Myson et d’autres ajoutent Pythagore. Selon Dicéarque, il y en a quatre sur qui tout le monde est d’accord : Thalès, Bias, Pittacos et Solon. Le même auteur en nomme six autres, parmi lesquels il en choisit trois : Aristodème, Pamphile, le Lacédémonien Chilon, Cléobule, Anacharsis et Périandre. D’autres ajoutent Acousilaos, Caba ou Scala, un Argien. Hermippe, dans son livre sur les sages, dit qu’ils furent dix-sept et que chacun en choisit sept selon ses préférences. Ce sont Solon, Thalès, Pittacos, Bias, Chilon, Cléobule, Périandre, Anacharsis, Acousilaos, Épiménide, Léophante, Phérécyde, Aristodème, Pythagore, Lasos, fils de Charmantidas ou de Sisambrinos ou, selon Aristoxène, de Chabrinus, Hermonée, Anaxagore. Hippobotos (Catalogue des Philosophes) les inscrit ainsi : Orphée, Linos, Solon, Périandre, Anacharsis, Cléobule, Myson, Thalès, Bias, Pittacos, Épicharme et Pythagore. Voici des lettres attribuées à Thalès[21] : ==== Thalès à Phérécyde ==== « J’apprends que vous vous disposez à présenter aux Grecs le premier traité ionien des choses divines. Vous agiriez peut-être plus sagement en lisant votre ouvrage à vos amis, qu’en communiquant à n’importe quelles gens des écrits qui ne peuvent guère leur être utiles. « Si cela vous plaît, j’aimerais profiter de vos recherches et, si vous m’y invitez, je viendrai vous trouver au plus tôt. Car Solon d’Athènes et moi, qui avons déjà traversé deux fois la mer pour aller visiter la Crète, et pour aller en Égypte nous entretenir avec les prêtres et les astronomes du lieu, nous sommes assez sages pour ne pas hésiter à la traverser de nouveau pour aller vous voir. « Je parle de Solon, parce qu’il viendra avec moi si vous le permettez. Vous êtes un sédentaire, vous allez rarement en Ionie, vous n’aimez guère aller voir les étrangers, et vous ne songez, j’imagine, qu’à écrire. « Mais nous qui n’écrivons pas, nous parcourons volontiers la Grèce et l’Italie. » ==== Thalès à Solon ==== « Si vous quittez Athènes, vous aurez, je crois, tout avantage à venir vous établir à Milet, parmi les colons athéniens. Il n’y a là pour vous aucun danger. Si vous hésitez, sous prétexte que nous, Milésiens, sommes gouvernés par un tyran (je sais que vous haïssez tout pouvoir absolu), songez du moins que vous aurez plaisir à vivre avec nous qui sommes vos amis. Je sais que Bias vous a écrit et vous invite à aller à Priène. Si vous trouvez préférable d’habiter la ville de Priène, j’irai vivre là-bas avec vous. » <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Ausone|'''Ausone''']] ''([[w:309|309]]/[[w:310|310]], à [[w:Bazas|Bazas]] ou à [[w:Bordeaux#Burdigala,_cité_romaine_(Ier_siècle_-_Ve_siècle)|Bordeaux]] — [[w:394|394]]/[[w:395|395]], entre [[w:Langon_(Gironde)|Langon]] et [[w:La_Réole|La Réole]])'' <sup>[[w:IVe_siècle|⏳]]</sup> [[s:Auteur:Ausone|<sup>📚</sup>]] == === Le Jeu des Sept Sages === :Thalès a trouvé [texte grec] pour nous défendre de nous porter cautions, parce qu'il y a du danger à répondre ainsi pour d'autres[iv]. Nous donnons-là un avis qui ne plaira pas beaucoup aux emprunteurs. :THALÈS : Je suis Thalès de Milet ; j'ai dit, comme le poète Pindare, que l'eau est, le principe de toute chose. C'est à moi que des pêcheurs donnèrent autrefois [un trépied d'or] qu'ils avaient tiré de la mer : ils m'avaient choisi pour obéir au dieu de Délos, qui envoyait ce présent à un sage. Je refusai de le recevoir, je le leur rendis pour le porter à d'autres que je croyais plus dignes. Envoyé à tous les sept Sages, et renvoyé par eux, il nie fut rapporté. Je le reçus alors pour le consacrer à Apollon : car si Phébus a voulu qu'on choisit un sage, ce n'était pas d'un homme, mais d'un dieu qu'il fallait l'entendre. Je suis donc ce Thalès : mais un motif m'amène sur la scène. Comme les deux sages qui m'ont précédé, je viens défendre la sentence dont je suis l'auteur. Elle déplaira, mais non certes aux esprits prudents que l'expérience a instruits et rendus plus avisés. Nous avons dit : [texte grec], ou, en latin : Cautionne, mais tu t'en trouveras mal. Je pourrais parcourir mille exemples pour vous montrer des cautions et des répondants bien et dûment convaincus de repentir. Mais je ne veux nommer personne. Que chacun de, vous ré-fléchisse, et compte en lui-même combien de gens ont perdu ou souffert de s'être ainsi portés cautions pour d'autres. Toutefois, si un pareil service a du charme pour vous, n'y renoncez ni les uns ni les autres. :Alors que les uns applaudissent, et que les autres, si je les blesse, me sifflent. :THALÈS DE MILET. :AVANT d'oser une mauvaise action, à défaut de Témoin redoute ta conscience. La vie s'éteint, mais la gloire de la mort ne meurt point. Ce que tu veux faire, abstiens-toi de le dire. C'est un supplice de craindre ce qu'on ne peut empêcher. Si tu blâmes avec raison, ton hostilité même est profitable ; si tu loues mal à propos, ton amitié même est nuisible. Rien de trop. - Arrêtons-nous, et qu'ici même il n'y ait rien de trop. <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == [[w:Proclus|'''Proclus''']] ''(le 7 ou 8 février [[w:412|412]], à [[w:Constantinople|Constantinople]] — le 17 avril [[w:485|485]], à [[w:Histoire_d%27Athènes#Antiquité_tardive|Athènes]])''<sup>[[w:Ve_siècle|⏳]]</sup> [[s:Auteur:Proclus|<sup>📚</sup>]] == === Commentaire sur le premier livre d'Euclide, 65, 3 === ὥσπερ οὖν παρὰ τοῖς Φοίνιξιν διὰ τὰς ἐμπορείας καὶ τὰ συναλλάγματα τὴν ἀρχὴν ἔλαβεν ἡ τῶν ἀριθμῶν ἀκριβὴς γνῶσις, οὕτω δὴ καὶ παρ' Αἰγυπτίοις ἡ γεωμετρία διὰ τὴν εἰρημένην αἰτίαν εὕρηται. Θαλῆς δὲ πρῶτον εἰς Αἴγυπτον ἐλθὼν μετήγαγεν εἰς τὴν Ἑλλάδα τὴν θεωρίαν ταύτην καὶ πολλὰ μὲν αὐτὸς εὗρεν, πολλῶν δὲ τὰς ἀρχὰς τοῖς μετ' αὐτὸν ὑφηγήσατο τοῖς μὲν καθολικώτερον ἐπιβάλλων, τοῖς δὲ αἰσθητικώτερον. <div style="text-align: center; margin: 0 auto; color: #3366BB">⁂</div> == « [[w:Souda|'''Suidas''']] » ''([[w:Floruit|{{Info|''fl.''|Floruit, locution latine que l'on emploie pour indiquer la période au cours de laquelle une personne a été active}}]] fin du [[w:IXe_siècle|IX<sup>ème</sup> siècle {{Info|EC|de l’Ère Commune}}]])'' [[s:Auteur:Suidas|<sup>📚</sup>]] == === La Souda === Θαλῆς, Ἐξαμύου καὶ Κλεοβουλίνης, Μιλήσιος, ὡς δὲ Ἡρόδοτος Φοῖνιξ: γεγονὼς πρὸ Κροίσου, ἐπὶ τῆς λε# ὀλυμπιάδος, κατὰ δὲ Φλέγοντα γνωριζόμενος ἤδη ἐπὶ τῆς ζ#. ἔγραψε περὶ μετεώρων ἐν ἔπεσι, Περὶ ἰσημερίας, καὶ ἄλλα πολλά. ἐτελεύτησε δὲ γηραιός, θεώμενος γυμνικὸν ἀγῶνα, πιληθεὶς δὲ ὑπὸ τοῦ ὄχλου καὶ ἐκλυθεὶς ὑπὸ τοῦ καύματος. πρῶτος δὲ Θαλῆς τὸ τοῦ σοφοῦ ἔσχεν ὄνομα καὶ πρῶτος τὴν ψυχὴν εἶπεν ἀθάνατον ἐκλείψεις τε καὶ ἰσημερίας κατείληφεν. ἀποφθέγματα δὲ αὐτοῦ πλεῖστα: καὶ τὸ θρυλλούμενον: γνῶθι σαυτόν. τὸ γάρ, ἐγγύα, πάρα δ' ἄτα, Χίλωνός ἐστι μᾶλλον, ἰδιοποιησαμένου αὐτό: καὶ τό, μηδὲν ἄγαν. 0s7njgbxk3hj3x948wz9r4l1xq6hzub Python pour le calcul scientifique/Annexe/Index 0 83349 768559 768480 2026-06-25T07:06:41Z Cdang 1202 /* I */ is 768559 wikitext text/x-wiki {{SommaireCompact}} == * == * <code>+</code> (plus) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>+=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>-</code> (moins) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|3]] * <code>-=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>*</code> (astérisque) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>**</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>/</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>//</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>^</code> (circonflexe) : [[../../Découverte_de_Python_et_de_Jupyter#cite_note-2|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&</code> (esperluette) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] * <code>|</code> (tube) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>|=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] == A == * <code>plt.add_subplot()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axis.add_patch()</code> : [[../../Graphiques#Dessins|1]] * aléatoire (générateur de nombres pseudo-~) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Graphiques|2]], [[../../Polynômes#Régression polynomiale|3]], [[../../Statistiques#Fréquence, histogramme|'''4''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]], [[../../Régression et optimisation#Régression linéaire|6]] * <code>and</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]], [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|2]] <br />voir [[#*|&]], [[#L|np.logical-all()]] * <code>all</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]] * <code>axes.annotate()</code> : [[../../Graphiques#Annotations|1]] * <code>np.arange()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Premier_tracé_graphique|1]], [[../../Éléments_de_programmation#Définition_en_compréhension|2]], [[../../Graphiques|3]], [[../../Manipulation_de_matrices#Définir_un_tenseur|'''3''']]<br /> → [[#R|<code>range()</code>]] * <code>np.array()</code> (classe <code>ndarray</code>) : [[../../Fonctions_mathématiques_générales#Vecteurs_et_matrices|1]], [[../../Éléments_de_programmation#Exploiter_le_contenu_d'un_fichier_texte|2]], [[../../Graphiques#Transformation_des_objets_graphiques|3]], [[../../Manipulation_de_matrices|'''4''']], [[../../Statistiques#Méthodes_de_matrices|5]], [[../../Algèbre_linéaire#Opérations_vectorielles|6]] * <code>float.as_integer_ratio()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]], [[../../Éléments_de_programmation#Réels|2]] * <code>fig.axes</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] == B == * <code>plt.bar()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * booléens : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Fonctions mathématiques générales#Fonctions booléennes|2]] * <code>break()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == C == * ''{{lang|en|callable}}'' (« appelable ») : [[../../Découverte_de_Python_et_de_Jupyter#Appelable_(callable)|1]] * <code>str.capitalize()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.center()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>chr()</code> : [[../../Éléments_de_programmation#Autres_fonctions|1]] * classe (d'un langage orienté objet) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * <code>color</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>fig.colorbar()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * complexe (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>plt.contour()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>plt.contourf()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>continue()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == D == * <code>def</code> : [[../../Éléments_de_programmation#Fonction|1]] * division euclidienne : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>divmode()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>dpi</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] == E == * <code>edgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>elif</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * <code>else</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * espace de nom (<code>np.</code>, <code>plt.</code>, <code>scipy.</code>…) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]], [[../../Découverte_de_Python_et_de_Jupyter#Les_espaces_de_noms|2]] == F == * <code>facecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>figsize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>fillstyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.find()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * fonction (définir une ~) : [[../../Éléments_de_programmation#Fonction|1]] * <code>for</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == G == * <code>plt.gca()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * <code>plt.gcf()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * GIF animé : [[../../Graphiques#Créer_un_GIF_animé|1]] == H == * <code>plt.hist()</code> : [[../../Graphiques|1]] * <code>html</code> (module) : [[../../Découverte_de_Python_et_de_Jupyter#Les_chaînes_de_caractères|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]] == I == * <code>if</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * imaginaire (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>in</code> : [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|1]], [[../../Éléments de programmation#Définition en compréhension|2]] * infini +∞, <code>float("inf")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * instance (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * installation d'un module : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * <code>is</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]] * <code>str.isdigit()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>float.is_integer()</code> : [[../../Éléments_de_programmation#Réels|1]] == J == * <code>j</code> (imaginaire) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] == L == * LaTeX : [[../../Graphiques#Mise_en_forme_du_texte|1]] * <code>layout</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>plt.legend()</code> : [[../../Graphiques|1]], [[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|2]] * <code>linear</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>linestyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>linewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.ljust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>log</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>np.logical_and()</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]]<br /> voir [[#A|and]] * <code>logit</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>str.lower()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>ls</code> (paramètre) : voir ''linestyle'' * <code>lw</code> (paramètre) : voir ''linewidth'' == M == * <code>marker</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markerfacecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markersize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * Matplotlib (module, <code>np</code>) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]]<br />→ Pyplot * <code>np.meshgrid()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * méthode (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * module : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] == N == * NaN ''(not a number)'', <code>float("nan")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>nextafter()</code> : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]] * <code>normal()</code> [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]] * <code>np.</code> : espace de nom, abréviation de <code>numpy.</code><br />→ Numpy * Numpy (module) : [[../../Découverte de Python et de Jupyter|1]] == P == * <code>str.partition()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * ''patch'' (pièce, dessin) : [[../../Graphiques#Dessins|1]] * <code>plt.pcolormesh()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>pip install</code> : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * PIL (module ''Python imaging library'') : [[../../Graphiques#Créer_un_GIF_animé|1]] * <code>plt.plot()</code> : [[../../Graphiques|1]] * <code>plt.plot_surface()</code> : [[../../Graphiques#Surfaces_3D|1]] * <code>plt</code> : espace de nom, abréviation de <code>pyplot.</code><br />→ <code>plt</code> * <code>plt.polar()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>pow()</code> : [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|1]] * puissance (élévation à la) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>pyplot</code> (<code>plt</code>, option de Matplotlib) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]] * PyQt (module) : [[../../Éléments_de_programmation#Avec_PyQt|1]] == Q == * <code>plt.quiver()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] == R == * <code>rand()</code> : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']] * <code>randn()</code> : [[../../Graphiques|1]], [[../../Polynômes#Régression polynomiale|2]], [[../../Statistiques#Fréquence, histogramme|'''3''']], [[../../Régression et optimisation#Régression linéaire|4]] * <code>random</code> (module NumPy) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|3]] * <code>range()</code> : [[../../Premiers programmes#Deuxième_programme|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]], [[../../Éléments_de_programmation#Définition_en_compréhension|3]], [[../../Éléments_de_programmation#Structures_de_contrôle|4]]<br /> → [[#A|<code>np.arange()</code>]] * <code>plt.rcParams()</code> : [[../../Graphiques#Mise_en_forme_du_texte|1]] * réel à virgule flottante (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>str.replace()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rfind()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rjust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == S == * <code>plt.savefig()</code> : [[../../Graphiques#Exemple|1]] * <code>line2D.set()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>axis.set_aspect()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>line2D.set_label()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>line2D.set_linestyle()</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>axes.set_rticks()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_thetagrids()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_title()</code> : [[../../Graphiques#Exemple|1]] * <code>axes.set_xlabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_xlim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_xsticks()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axes.set_ylabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_ylim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_minor_formatter()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * ''shebang'' : [[../../Découverte de Python et de Jupyter|1]] * <code>plt.show()</code> : [[../../Graphiques|1]] * <code>str.split()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>plt.step()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * <code>plt.subplots()</code> : [[../../Graphiques#Exemple|1]] * <code>fig.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>plt.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>symlog</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] == T == * <code>plt.text()</code> : [[../../Graphiques#Annotations|1]] * <code>plt.title()</code> : [[../../Graphiques|1]] * <code>aix.transData()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>transform</code> (paramètre) : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>matplotlib.transforms()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * Tk (module) : [[../../Éléments_de_programmation#Avec_Tk|1]] == U == * <code>str.upper()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == W == * <code>while</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == X == * <code>plt.xlabel()</code> : [[../../Graphiques|1]] == Y == * <code>plt.ylabel()</code> : [[../../Graphiques|1]] ---- [[../../Traitement d'images|Traitement d'images]] &lt; [[../../|↑]] &gt; [[Catégorie:Python pour le calcul scientifique (livre)]] 8reowf4qqf2sncjyrueymt68or60f5r 768562 768559 2026-06-25T07:17:09Z Cdang 1202 /* A */ typo 768562 wikitext text/x-wiki {{SommaireCompact}} == * == * <code>+</code> (plus) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>+=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>-</code> (moins) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|3]] * <code>-=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>*</code> (astérisque) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>**</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>/</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>//</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>^</code> (circonflexe) : [[../../Découverte_de_Python_et_de_Jupyter#cite_note-2|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&</code> (esperluette) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] * <code>|</code> (tube) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>|=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] == A == * <code>plt.add_subplot()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axis.add_patch()</code> : [[../../Graphiques#Dessins|1]] * aléatoire (générateur de nombres pseudo-~) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Graphiques|2]], [[../../Polynômes#Régression polynomiale|3]], [[../../Statistiques#Fréquence, histogramme|'''4''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]], [[../../Régression et optimisation#Régression linéaire|6]] * <code>and</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]], [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|2]] <br />voir [[#*|&]], [[#L|np.logical_all()]] * <code>all</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]] * <code>axes.annotate()</code> : [[../../Graphiques#Annotations|1]] * <code>np.arange()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Premier_tracé_graphique|1]], [[../../Éléments_de_programmation#Définition_en_compréhension|2]], [[../../Graphiques|3]], [[../../Manipulation_de_matrices#Définir_un_tenseur|'''3''']]<br /> → [[#R|<code>range()</code>]] * <code>np.array()</code> (classe <code>ndarray</code>) : [[../../Fonctions_mathématiques_générales#Vecteurs_et_matrices|1]], [[../../Éléments_de_programmation#Exploiter_le_contenu_d'un_fichier_texte|2]], [[../../Graphiques#Transformation_des_objets_graphiques|3]], [[../../Manipulation_de_matrices|'''4''']], [[../../Statistiques#Méthodes_de_matrices|5]], [[../../Algèbre_linéaire#Opérations_vectorielles|6]] * <code>float.as_integer_ratio()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]], [[../../Éléments_de_programmation#Réels|2]] * <code>fig.axes</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] == B == * <code>plt.bar()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * booléens : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Fonctions mathématiques générales#Fonctions booléennes|2]] * <code>break()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == C == * ''{{lang|en|callable}}'' (« appelable ») : [[../../Découverte_de_Python_et_de_Jupyter#Appelable_(callable)|1]] * <code>str.capitalize()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.center()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>chr()</code> : [[../../Éléments_de_programmation#Autres_fonctions|1]] * classe (d'un langage orienté objet) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * <code>color</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>fig.colorbar()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * complexe (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>plt.contour()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>plt.contourf()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>continue()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == D == * <code>def</code> : [[../../Éléments_de_programmation#Fonction|1]] * division euclidienne : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>divmode()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>dpi</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] == E == * <code>edgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>elif</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * <code>else</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * espace de nom (<code>np.</code>, <code>plt.</code>, <code>scipy.</code>…) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]], [[../../Découverte_de_Python_et_de_Jupyter#Les_espaces_de_noms|2]] == F == * <code>facecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>figsize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>fillstyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.find()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * fonction (définir une ~) : [[../../Éléments_de_programmation#Fonction|1]] * <code>for</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == G == * <code>plt.gca()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * <code>plt.gcf()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * GIF animé : [[../../Graphiques#Créer_un_GIF_animé|1]] == H == * <code>plt.hist()</code> : [[../../Graphiques|1]] * <code>html</code> (module) : [[../../Découverte_de_Python_et_de_Jupyter#Les_chaînes_de_caractères|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]] == I == * <code>if</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * imaginaire (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>in</code> : [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|1]], [[../../Éléments de programmation#Définition en compréhension|2]] * infini +∞, <code>float("inf")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * instance (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * installation d'un module : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * <code>is</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]] * <code>str.isdigit()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>float.is_integer()</code> : [[../../Éléments_de_programmation#Réels|1]] == J == * <code>j</code> (imaginaire) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] == L == * LaTeX : [[../../Graphiques#Mise_en_forme_du_texte|1]] * <code>layout</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>plt.legend()</code> : [[../../Graphiques|1]], [[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|2]] * <code>linear</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>linestyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>linewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.ljust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>log</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>np.logical_and()</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]]<br /> voir [[#A|and]] * <code>logit</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>str.lower()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>ls</code> (paramètre) : voir ''linestyle'' * <code>lw</code> (paramètre) : voir ''linewidth'' == M == * <code>marker</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markerfacecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markersize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * Matplotlib (module, <code>np</code>) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]]<br />→ Pyplot * <code>np.meshgrid()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * méthode (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * module : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] == N == * NaN ''(not a number)'', <code>float("nan")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>nextafter()</code> : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]] * <code>normal()</code> [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]] * <code>np.</code> : espace de nom, abréviation de <code>numpy.</code><br />→ Numpy * Numpy (module) : [[../../Découverte de Python et de Jupyter|1]] == P == * <code>str.partition()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * ''patch'' (pièce, dessin) : [[../../Graphiques#Dessins|1]] * <code>plt.pcolormesh()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>pip install</code> : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * PIL (module ''Python imaging library'') : [[../../Graphiques#Créer_un_GIF_animé|1]] * <code>plt.plot()</code> : [[../../Graphiques|1]] * <code>plt.plot_surface()</code> : [[../../Graphiques#Surfaces_3D|1]] * <code>plt</code> : espace de nom, abréviation de <code>pyplot.</code><br />→ <code>plt</code> * <code>plt.polar()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>pow()</code> : [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|1]] * puissance (élévation à la) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>pyplot</code> (<code>plt</code>, option de Matplotlib) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]] * PyQt (module) : [[../../Éléments_de_programmation#Avec_PyQt|1]] == Q == * <code>plt.quiver()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] == R == * <code>rand()</code> : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']] * <code>randn()</code> : [[../../Graphiques|1]], [[../../Polynômes#Régression polynomiale|2]], [[../../Statistiques#Fréquence, histogramme|'''3''']], [[../../Régression et optimisation#Régression linéaire|4]] * <code>random</code> (module NumPy) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|3]] * <code>range()</code> : [[../../Premiers programmes#Deuxième_programme|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]], [[../../Éléments_de_programmation#Définition_en_compréhension|3]], [[../../Éléments_de_programmation#Structures_de_contrôle|4]]<br /> → [[#A|<code>np.arange()</code>]] * <code>plt.rcParams()</code> : [[../../Graphiques#Mise_en_forme_du_texte|1]] * réel à virgule flottante (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>str.replace()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rfind()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rjust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == S == * <code>plt.savefig()</code> : [[../../Graphiques#Exemple|1]] * <code>line2D.set()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>axis.set_aspect()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>line2D.set_label()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>line2D.set_linestyle()</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>axes.set_rticks()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_thetagrids()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_title()</code> : [[../../Graphiques#Exemple|1]] * <code>axes.set_xlabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_xlim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_xsticks()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axes.set_ylabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_ylim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_minor_formatter()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * ''shebang'' : [[../../Découverte de Python et de Jupyter|1]] * <code>plt.show()</code> : [[../../Graphiques|1]] * <code>str.split()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>plt.step()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * <code>plt.subplots()</code> : [[../../Graphiques#Exemple|1]] * <code>fig.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>plt.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>symlog</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] == T == * <code>plt.text()</code> : [[../../Graphiques#Annotations|1]] * <code>plt.title()</code> : [[../../Graphiques|1]] * <code>aix.transData()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>transform</code> (paramètre) : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>matplotlib.transforms()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * Tk (module) : [[../../Éléments_de_programmation#Avec_Tk|1]] == U == * <code>str.upper()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == W == * <code>while</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == X == * <code>plt.xlabel()</code> : [[../../Graphiques|1]] == Y == * <code>plt.ylabel()</code> : [[../../Graphiques|1]] ---- [[../../Traitement d'images|Traitement d'images]] &lt; [[../../|↑]] &gt; [[Catégorie:Python pour le calcul scientifique (livre)]] pk3hkrlh7btl7v70swe0jikjwaz8h2r 768563 768562 2026-06-25T07:38:00Z Cdang 1202 /* C */ compréhension 768563 wikitext text/x-wiki {{SommaireCompact}} == * == * <code>+</code> (plus) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>+=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>-</code> (moins) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|3]] * <code>-=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>*</code> (astérisque) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>**</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>/</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>//</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>^</code> (circonflexe) : [[../../Découverte_de_Python_et_de_Jupyter#cite_note-2|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&</code> (esperluette) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] * <code>|</code> (tube) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>|=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] == A == * <code>plt.add_subplot()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axis.add_patch()</code> : [[../../Graphiques#Dessins|1]] * aléatoire (générateur de nombres pseudo-~) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Graphiques|2]], [[../../Polynômes#Régression polynomiale|3]], [[../../Statistiques#Fréquence, histogramme|'''4''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]], [[../../Régression et optimisation#Régression linéaire|6]] * <code>and</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]], [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|2]] <br />voir [[#*|&]], [[#L|np.logical_all()]] * <code>all</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]] * <code>axes.annotate()</code> : [[../../Graphiques#Annotations|1]] * <code>np.arange()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Premier_tracé_graphique|1]], [[../../Éléments_de_programmation#Définition_en_compréhension|2]], [[../../Graphiques|3]], [[../../Manipulation_de_matrices#Définir_un_tenseur|'''3''']]<br /> → [[#R|<code>range()</code>]] * <code>np.array()</code> (classe <code>ndarray</code>) : [[../../Fonctions_mathématiques_générales#Vecteurs_et_matrices|1]], [[../../Éléments_de_programmation#Exploiter_le_contenu_d'un_fichier_texte|2]], [[../../Graphiques#Transformation_des_objets_graphiques|3]], [[../../Manipulation_de_matrices|'''4''']], [[../../Statistiques#Méthodes_de_matrices|5]], [[../../Algèbre_linéaire#Opérations_vectorielles|6]] * <code>float.as_integer_ratio()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]], [[../../Éléments_de_programmation#Réels|2]] * <code>fig.axes</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] == B == * <code>plt.bar()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * booléens : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Fonctions mathématiques générales#Fonctions booléennes|2]] * <code>break()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == C == * ''{{lang|en|callable}}'' (« appelable ») : [[../../Découverte_de_Python_et_de_Jupyter#Appelable_(callable)|1]] * <code>str.capitalize()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.center()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>chr()</code> : [[../../Éléments_de_programmation#Autres_fonctions|1]] * classe (d'un langage orienté objet) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * <code>color</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>fig.colorbar()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * complexe (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * compréhension (définition en) : [[../../Éléments_de_programmation#Définition_en_compréhension|1]] * <code>plt.contour()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>plt.contourf()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>continue()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == D == * <code>def</code> : [[../../Éléments_de_programmation#Fonction|1]] * division euclidienne : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>divmode()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>dpi</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] == E == * <code>edgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>elif</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * <code>else</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * espace de nom (<code>np.</code>, <code>plt.</code>, <code>scipy.</code>…) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]], [[../../Découverte_de_Python_et_de_Jupyter#Les_espaces_de_noms|2]] == F == * <code>facecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>figsize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>fillstyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.find()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * fonction (définir une ~) : [[../../Éléments_de_programmation#Fonction|1]] * <code>for</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == G == * <code>plt.gca()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * <code>plt.gcf()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * GIF animé : [[../../Graphiques#Créer_un_GIF_animé|1]] == H == * <code>plt.hist()</code> : [[../../Graphiques|1]] * <code>html</code> (module) : [[../../Découverte_de_Python_et_de_Jupyter#Les_chaînes_de_caractères|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]] == I == * <code>if</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * imaginaire (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>in</code> : [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|1]], [[../../Éléments de programmation#Définition en compréhension|2]] * infini +∞, <code>float("inf")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * instance (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * installation d'un module : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * <code>is</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]] * <code>str.isdigit()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>float.is_integer()</code> : [[../../Éléments_de_programmation#Réels|1]] == J == * <code>j</code> (imaginaire) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] == L == * LaTeX : [[../../Graphiques#Mise_en_forme_du_texte|1]] * <code>layout</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>plt.legend()</code> : [[../../Graphiques|1]], [[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|2]] * <code>linear</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>linestyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>linewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.ljust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>log</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>np.logical_and()</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]]<br /> voir [[#A|and]] * <code>logit</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>str.lower()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>ls</code> (paramètre) : voir ''linestyle'' * <code>lw</code> (paramètre) : voir ''linewidth'' == M == * <code>marker</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markerfacecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markersize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * Matplotlib (module, <code>np</code>) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]]<br />→ Pyplot * <code>np.meshgrid()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * méthode (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * module : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] == N == * NaN ''(not a number)'', <code>float("nan")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>nextafter()</code> : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]] * <code>normal()</code> [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]] * <code>np.</code> : espace de nom, abréviation de <code>numpy.</code><br />→ Numpy * Numpy (module) : [[../../Découverte de Python et de Jupyter|1]] == P == * <code>str.partition()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * ''patch'' (pièce, dessin) : [[../../Graphiques#Dessins|1]] * <code>plt.pcolormesh()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>pip install</code> : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * PIL (module ''Python imaging library'') : [[../../Graphiques#Créer_un_GIF_animé|1]] * <code>plt.plot()</code> : [[../../Graphiques|1]] * <code>plt.plot_surface()</code> : [[../../Graphiques#Surfaces_3D|1]] * <code>plt</code> : espace de nom, abréviation de <code>pyplot.</code><br />→ <code>plt</code> * <code>plt.polar()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>pow()</code> : [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|1]] * puissance (élévation à la) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>pyplot</code> (<code>plt</code>, option de Matplotlib) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]] * PyQt (module) : [[../../Éléments_de_programmation#Avec_PyQt|1]] == Q == * <code>plt.quiver()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] == R == * <code>rand()</code> : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']] * <code>randn()</code> : [[../../Graphiques|1]], [[../../Polynômes#Régression polynomiale|2]], [[../../Statistiques#Fréquence, histogramme|'''3''']], [[../../Régression et optimisation#Régression linéaire|4]] * <code>random</code> (module NumPy) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|3]] * <code>range()</code> : [[../../Premiers programmes#Deuxième_programme|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]], [[../../Éléments_de_programmation#Définition_en_compréhension|3]], [[../../Éléments_de_programmation#Structures_de_contrôle|4]]<br /> → [[#A|<code>np.arange()</code>]] * <code>plt.rcParams()</code> : [[../../Graphiques#Mise_en_forme_du_texte|1]] * réel à virgule flottante (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>str.replace()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rfind()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rjust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == S == * <code>plt.savefig()</code> : [[../../Graphiques#Exemple|1]] * <code>line2D.set()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>axis.set_aspect()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>line2D.set_label()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>line2D.set_linestyle()</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>axes.set_rticks()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_thetagrids()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_title()</code> : [[../../Graphiques#Exemple|1]] * <code>axes.set_xlabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_xlim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_xsticks()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axes.set_ylabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_ylim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_minor_formatter()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * ''shebang'' : [[../../Découverte de Python et de Jupyter|1]] * <code>plt.show()</code> : [[../../Graphiques|1]] * <code>str.split()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>plt.step()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * <code>plt.subplots()</code> : [[../../Graphiques#Exemple|1]] * <code>fig.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>plt.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>symlog</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] == T == * <code>plt.text()</code> : [[../../Graphiques#Annotations|1]] * <code>plt.title()</code> : [[../../Graphiques|1]] * <code>aix.transData()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>transform</code> (paramètre) : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>matplotlib.transforms()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * Tk (module) : [[../../Éléments_de_programmation#Avec_Tk|1]] == U == * <code>str.upper()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == W == * <code>while</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == X == * <code>plt.xlabel()</code> : [[../../Graphiques|1]] == Y == * <code>plt.ylabel()</code> : [[../../Graphiques|1]] ---- [[../../Traitement d'images|Traitement d'images]] &lt; [[../../|↑]] &gt; [[Catégorie:Python pour le calcul scientifique (livre)]] 2jdhd1jlo7o047yhw2ecmtit2rspsig 768575 768563 2026-06-25T08:42:21Z Cdang 1202 itérable, enumerate(), zip() 768575 wikitext text/x-wiki {{SommaireCompact}} == * == * <code>+</code> (plus) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>+=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>-</code> (moins) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|3]] * <code>-=</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>*</code> (astérisque) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>**</code> : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>/</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>//</code> (barre de fraction) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>^</code> (circonflexe) : [[../../Découverte_de_Python_et_de_Jupyter#cite_note-2|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&</code> (esperluette) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>&=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] * <code>|</code> (tube) : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|2]] * <code>|=</code> : [[../../Découverte de Python et de Jupyter#Les booléens|1]] == A == * <code>plt.add_subplot()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axis.add_patch()</code> : [[../../Graphiques#Dessins|1]] * aléatoire (générateur de nombres pseudo-~) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Graphiques|2]], [[../../Polynômes#Régression polynomiale|3]], [[../../Statistiques#Fréquence, histogramme|'''4''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]], [[../../Régression et optimisation#Régression linéaire|6]] * <code>and</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]], [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|2]] <br />voir [[#*|&]], [[#L|np.logical_all()]] * <code>all</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]] * <code>axes.annotate()</code> : [[../../Graphiques#Annotations|1]] * <code>np.arange()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Premier_tracé_graphique|1]], [[../../Éléments_de_programmation#Définition_en_compréhension|2]], [[../../Graphiques|3]], [[../../Manipulation_de_matrices#Définir_un_tenseur|'''3''']]<br /> → [[#R|<code>range()</code>]] * <code>np.array()</code> (classe <code>ndarray</code>) : [[../../Fonctions_mathématiques_générales#Vecteurs_et_matrices|1]], [[../../Éléments_de_programmation#Exploiter_le_contenu_d'un_fichier_texte|2]], [[../../Graphiques#Transformation_des_objets_graphiques|3]], [[../../Manipulation_de_matrices|'''4''']], [[../../Statistiques#Méthodes_de_matrices|5]], [[../../Algèbre_linéaire#Opérations_vectorielles|6]] * <code>float.as_integer_ratio()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]], [[../../Éléments_de_programmation#Réels|2]] * <code>fig.axes</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] == B == * <code>plt.bar()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * booléens : [[../../Découverte de Python et de Jupyter#Les booléens|1]], [[../../Fonctions mathématiques générales#Fonctions booléennes|2]] * <code>break()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == C == * ''{{lang|en|callable}}'' (« appelable ») : [[../../Découverte_de_Python_et_de_Jupyter#Appelable_(callable)|1]] * <code>str.capitalize()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.center()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>chr()</code> : [[../../Éléments_de_programmation#Autres_fonctions|1]] * classe (d'un langage orienté objet) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * <code>color</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>fig.colorbar()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * complexe (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * compréhension (définition en) : [[../../Éléments_de_programmation#Définition_en_compréhension|1]] * <code>plt.contour()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>plt.contourf()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>continue()</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == D == * <code>def</code> : [[../../Éléments_de_programmation#Fonction|1]] * division euclidienne : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>divmode()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>dpi</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] == E == * <code>edgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>elif</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * <code>else</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * <code>enumerate()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Itérable|1]] * espace de nom (<code>np.</code>, <code>plt.</code>, <code>scipy.</code>…) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]], [[../../Découverte_de_Python_et_de_Jupyter#Les_espaces_de_noms|2]] == F == * <code>facecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>figsize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>fillstyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.find()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * fonction (définir une ~) : [[../../Éléments_de_programmation#Fonction|1]] * <code>for</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == G == * <code>plt.gca()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * <code>plt.gcf()</code> : [[../../Graphiques#Styles_de_codage_«_pyplot_»_et_«_OO_»|1]] * GIF animé : [[../../Graphiques#Créer_un_GIF_animé|1]] == H == * <code>plt.hist()</code> : [[../../Graphiques|1]] * <code>html</code> (module) : [[../../Découverte_de_Python_et_de_Jupyter#Les_chaînes_de_caractères|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]] == I == * <code>if</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] * imaginaire (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>in</code> : [[../../Découverte de Python et de Jupyter#Les ensembles et les dictionnaires|1]], [[../../Éléments de programmation#Définition en compréhension|2]] * infini +∞, <code>float("inf")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * instance (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * installation d'un module : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * <code>is</code> : [[../../Découverte_de_Python_et_de_Jupyter#Les_booléens|1]] * <code>str.isdigit()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>float.is_integer()</code> : [[../../Éléments_de_programmation#Réels|1]] * itérable : [[../../Découverte_de_Python_et_de_Jupyter#Itérable|1]] == J == * <code>j</code> (imaginaire) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] == L == * LaTeX : [[../../Graphiques#Mise_en_forme_du_texte|1]] * <code>layout</code> (paramètre) : [[../../Graphiques#Mise_en_forme_de_la_figure|1]] * <code>plt.legend()</code> : [[../../Graphiques|1]], [[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|2]] * <code>linear</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>linestyle</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>linewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>str.ljust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>log</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>np.logical_and()</code> : [[../../Fonctions_mathématiques_générales#Fonctions_booléennes|1]]<br /> voir [[#A|and]] * <code>logit</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>str.lower()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>ls</code> (paramètre) : voir ''linestyle'' * <code>lw</code> (paramètre) : voir ''linewidth'' == M == * <code>marker</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markeredgewidth</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markerfacecolor</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>markersize</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * Matplotlib (module, <code>np</code>) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]]<br />→ Pyplot * <code>np.meshgrid()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * méthode (d'une classe) : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] * module : [[../../Découverte_de_Python_et_de_Jupyter#Vocabulaire|1]] == N == * NaN ''(not a number)'', <code>float("nan")</code> : [[../../Découverte_de_Python_et_de_Jupyter#Commandes_élémentaires|1]] * <code>nextafter()</code> : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]] * <code>normal()</code> [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|5]] * <code>np.</code> : espace de nom, abréviation de <code>numpy.</code><br />→ Numpy * Numpy (module) : [[../../Découverte de Python et de Jupyter|1]] == P == * <code>str.partition()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * ''patch'' (pièce, dessin) : [[../../Graphiques#Dessins|1]] * <code>plt.pcolormesh()</code> : [[../../Graphiques#Cartes_de_valeurs_et_de_vecteurs|1]] * <code>pip install</code> : [[../../Découverte de Python et de Jupyter#Installation et mise à jour de modules|1]] * PIL (module ''Python imaging library'') : [[../../Graphiques#Créer_un_GIF_animé|1]] * <code>plt.plot()</code> : [[../../Graphiques|1]] * <code>plt.plot_surface()</code> : [[../../Graphiques#Surfaces_3D|1]] * <code>plt</code> : espace de nom, abréviation de <code>pyplot.</code><br />→ <code>plt</code> * <code>plt.polar()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>pow()</code> : [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|1]] * puissance (élévation à la) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]], [[../../Fonctions mathématiques générales#Rappel sur les opérations de base|2]] * <code>pyplot</code> (<code>plt</code>, option de Matplotlib) : [[../../Découverte de Python et de Jupyter#Premier tracé graphique|1]], [[../../Graphiques|2]] * PyQt (module) : [[../../Éléments_de_programmation#Avec_PyQt|1]] == Q == * <code>plt.quiver()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] == R == * <code>rand()</code> : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']] * <code>randn()</code> : [[../../Graphiques|1]], [[../../Polynômes#Régression polynomiale|2]], [[../../Statistiques#Fréquence, histogramme|'''3''']], [[../../Régression et optimisation#Régression linéaire|4]] * <code>random</code> (module NumPy) : [[../../Éléments de programmation#Utilisation de Pandas|1]], [[../../Statistiques#Fréquence, histogramme|'''2''']], [[../../Interpolation, extrapolation et lissage#Avec le module scipy.signal|3]] * <code>range()</code> : [[../../Premiers programmes#Deuxième_programme|1]], [[../../Éléments_de_programmation#Autres_fonctions|2]], [[../../Éléments_de_programmation#Définition_en_compréhension|3]], [[../../Éléments_de_programmation#Structures_de_contrôle|4]]<br /> → [[#A|<code>np.arange()</code>]] * <code>plt.rcParams()</code> : [[../../Graphiques#Mise_en_forme_du_texte|1]] * réel à virgule flottante (nombre) : [[../../Découverte de Python et de Jupyter#Commandes élémentaires|1]] * <code>str.replace()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rfind()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>str.rjust()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == S == * <code>plt.savefig()</code> : [[../../Graphiques#Exemple|1]] * <code>line2D.set()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>axis.set_aspect()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>line2D.set_label()</code>[[../../Graphiques#Plusieurs_courbes_sur_un_même_système_d'axes|1]] * <code>line2D.set_linestyle()</code> (paramètre) : [[../../Graphiques#Mise_en_forme_d'une_courbe|1]] * <code>axes.set_rticks()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_thetagrids()</code> : [[../../Graphiques#Tracé_polaire|1]] * <code>axes.set_title()</code> : [[../../Graphiques#Exemple|1]] * <code>axes.set_xlabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_xlim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_xsticks()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * <code>axes.set_ylabel()</code> : [[../../Graphiques#Exemple|1]] * <code>axis.set_ylim()</code> : [[../../Graphiques#Dessins|1]] * <code>axis.set_minor_formatter()</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] * ''shebang'' : [[../../Découverte de Python et de Jupyter|1]] * <code>plt.show()</code> : [[../../Graphiques|1]] * <code>str.split()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] * <code>plt.step()</code> : [[../../Graphiques#Autres_exemples_de_tracés_2D|1]] * <code>plt.subplots()</code> : [[../../Graphiques#Exemple|1]] * <code>fig.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>plt.suptitle()</code> : [[../../Graphiques#Exemple|1]] * <code>symlog</code> (paramètre) : [[../../Graphiques#Gestion_des_échelles|1]] == T == * <code>plt.text()</code> : [[../../Graphiques#Annotations|1]] * <code>plt.title()</code> : [[../../Graphiques|1]] * <code>aix.transData()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>transform</code> (paramètre) : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * <code>matplotlib.transforms()</code> : [[../../Graphiques#Transformation_des_objets_graphiques|1]] * Tk (module) : [[../../Éléments_de_programmation#Avec_Tk|1]] == U == * <code>str.upper()</code> : [[../../Éléments_de_programmation#Méthodes_des_chaînes|1]] == W == * <code>while</code> : [[../../Éléments_de_programmation#Structures_de_contrôle|1]] == X == * <code>plt.xlabel()</code> : [[../../Graphiques|1]] == Y == * <code>plt.ylabel()</code> : [[../../Graphiques|1]] == Z == * <code>zip()</code> : [[../../Découverte_de_Python_et_de_Jupyter#Itérable|1]] ---- [[../../Traitement d'images|Traitement d'images]] &lt; [[../../|↑]] &gt; [[Catégorie:Python pour le calcul scientifique (livre)]] dk990mpj5jjtwqa4hn8czwnruwpoq3i Wikilivres:Le Bistro/2026 4 83439 768489 767711 2026-06-24T15:17:02Z Fourmidable 92370 /* La catégorie SI est pleine ! */ Réponse 768489 wikitext text/x-wiki <noinclude>{{Wikilivres:Le Bistro/En-tête}}</noinclude> == Le meilleur à Wikilivres pour 2026 ! == Je crée la page du bistrot 2026 en transmettant mes vœux. Bonne année éditoriale à tous ! [[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> 16 janvier 2026 à 06:05 (CET) :Merci, meilleurs vœux ! [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 16 janvier 2026 à 09:53 (CET) ::Meilleurs vœux pour 2026 ! ::--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 16 janvier 2026 à 20:13 (CET) :::Meilleurs vœux pour 2026 :::[[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 17 janvier 2026 à 11:56 (CET) ::::Bonne année et bonnes lectures et écritures ! ::::[[Utilisateur:Matthius|Matthius]] == Page orpheline du livre : États généraux du multilinguisme dans les outre-mer == Bonjour, Le livre "États généraux du multilinguisme dans les outre-mer" a laissé quelques pages orphelines. Ces pages orphelines ont été recopiées ou regroupé dans le texte du livre. Elles font donc doublons. Je montre dans ce livre [[feuilles dupliquées orphelines|feuilles dupliquées orphelines]] cette suite de pages orphelines accompagnée par la page où elles ont été recopiées. Merci [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 10 février 2026 à 19:57 (CET) :@[[Utilisateur:Xhungab|Xhungab]], @[[Utilisateur:JackPotte|JackPotte]], @[[Utilisateur:DavidL|DavidL]], bonjour. Je vois que l'on est en campagne de gestion de pages orphelines et je me pose la question de savoir si on doit les supprimer une fois réintégrées ailleurs ou les supprimer ? Je vois que les deux cas de figure ont été mis en œuvre précédemment. L'avantage de la fusion est de conserver l'historique des modifications, mais je ne suis pas habitué à le faire. Si quelqu'un d'entre vous pouvait m'indiquer une page d'explication, cela m'aiderait à m'y mettre. Ensuite, je me demande si la fusion a vraiment un sens lorsque c'est la même personne qui a créé la page orpheline et la nouvelle au contenu copié collé pour insertion ? Qu'en pensez-vous ? [[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> 13 février 2026 à 16:02 (CET) ::Petit complément. J'ai créé ce livre ::* [[feuilles volantes orphelines|Feuilles Volantes Orphelines]] ::dans lequel je met ::* les feuilles volantes qui sont dans les pages orphelines. ::* les mini livres qui sont dans les pages orphelines. ::* les livres abandonnés sur une feuille. qui sont dans les pages orphelines. ::Mon intention est d'indiquer si c'est un mini livre, si c'est une feuille sans contenue, et rien si c'est une simple feuille volante. [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 13 février 2026 à 16:16 (CET) :::Ça fait plaisir de voir une personne s'investir dans la maintenance de Wikilivres =). Soit le ou la bienvenu.e. J'attends les avis des autres administrateurs pour te donner mon soutien @[[Utilisateur:Xhungab|Xhungab]]. @ bientôt. [[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> 13 février 2026 à 16:27 (CET) :::: @[[Utilisateur:Lionel Scheepmans|Lionel Scheepmans]] bonjour, je n'ai pas compris en quoi "deux cas de figure ont été mis en œuvre précédemment" : si la page est vide on la supprime et si elle a au moins une phrase valable on la fusionne. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 13 février 2026 à 16:26 (CET) ::::Concernant l'auteur pour moi cela importe peu, car on souhaite conserver chaque élément permettant de prouver une primauté du droit d'auteur (par exemple pour ne pas accuser un contributeur d'avoir copié un site miroir de sa page supprimée). [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 13 février 2026 à 16:30 (CET) :::::Désolé [[Utilisateur:JackPotte|JackPotte]], je n'avais pas vu que les pages que tu as supprimées étaient vides. La règle est donc supprimer si vide et fusionner si non. Tu peux me conseiller une page d'aide pour oppérer les fusions ? [[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> 13 février 2026 à 16:34 (CET) ::::::Salut, ::::::Pour la fusion d'historique : ::::::* soit [[Wikilivres:Le guide de l'administrateur#Fusionner deux pages|Utiliser la page spéciale pour cela]] (cependant ne fonctionne pas toujours), ::::::* soit [https://fr.wikibooks.org/w/index.php?title=Wikilivres:Le_guide_de_l%27administrateur&oldid=588957#Fusionner_deux_pages utiliser l'ancienne méthode] qui reste toujours valable quand la première ne fonctionne pas. ::::::--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 13 février 2026 à 19:34 (CET) :::::::Salut @[[Utilisateur:DavidL|DavidL]]. Je viens de tester l'outil fusionner avec les deux pages suivantes :::::::[[États généraux du multilinguisme dans les outre-mer/Annexes/Contexte]] :::::::vers :::::::[[États généraux du multilinguisme dans les outre-mer/Annexes]] :::::::Et j'ai le message suivant : :::::::« La période spécifiée chevauche les versions préexistantes de la page de destination. » :::::::Tu peux m'expliquer si tu as le temps ? [[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> 16 février 2026 à 00:19 (CET) ::::::::Salut @[[Utilisateur:Lionel Scheepmans|Lionel Scheepmans]] ::::::::Dans ce cas, j'utilise [https://fr.wikibooks.org/w/index.php?title=Wikilivres:Le_guide_de_l%27administrateur&oldid=588957#Fusionner_deux_pages l'ancienne méthode]. ::::::::# Renommer A (page qui n'existera plus) vers B, sans laisser de redirection, et en cochant la case de suppression de B (case qui apparait après 1er clic sur le bouton Renommer) (&rarr; l'historique de A est sur B, celui de B est supprimé) ::::::::# Supprimer B (&rarr; l'historique de A et B sont supprimés) ::::::::# Restaurer B et cocher toutes les cases des versions (bouton "Inverser la sélection") (&rarr; restaurer l'historique de A et B) ::::::::# Effectuer une modification de la version finale de la page à afficher, trouvée dans l'historique. ::::::::--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 16 février 2026 à 08:29 (CET) :::::::::Merci @[[Utilisateur:DavidL|DavidL]]. Ça m'a l'air bien compliqué tout ça. Et avec toute les pages qu'il faut faire, ça risque de prendre un certain temps. Je manque de courage en pensant que c'est juste pour ajouter une ligne dans l'historique des versions. [[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> 16 février 2026 à 14:51 (CET) == archive.today == ''Voir [[:w:Wikipédia:Le Bistro/21 février 2026#archive.today]].'' Ce site pose apparemment des problèmes de sécurité, il faudrait le remplacer au plus vite si possible, et sinon désactiver les liens. [[Utilisateur:SyntaxTerror|SyntaxTerror]] ([[Discussion utilisateur:SyntaxTerror|discussion]]) 21 février 2026 à 13:07 (CET) :Salut SyntaxTerror, :* 0 liens vers archive.today : [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.today] :* <s>18</s> 0 liens vers archive.is [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.is] :* 0 liens vers archive.li [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.li] :* 0 liens vers archive.ec [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.ec] :* 0 liens vers archive.ph [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.ph] :* 0 liens vers archive.fo [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.fo] :* 0 liens vers archive.md [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.md] :* <s>1</s> 0 liens vers archive.vn [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.vn] :* 0 liens vers archive.closed.social [https://fr.wikibooks.org/wiki/Sp%C3%A9cial:Recherche_de_lien?target=archive.closed.social] :--&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 21 février 2026 à 16:14 (CET) ::Merci pour vos efforts ! [[Utilisateur:Fourmidable|Fourmidable]] ([[Discussion utilisateur:Fourmidable|discussion]]) 12 mai 2026 à 16:22 (CEST) == demande aide pour sortir du mode "ébauche" == je viens de terminer mon livre ... ci dessous. J'ai compris qu'il fallait le signaler comme n'étant plus au stade "ébauche" commente faire Merci https://fr.wikibooks.org/w/index.php?title=Essai_pour_un_mod%C3%A8le_de_psychisme_objectif&action=info#mw-pageinfo-header-basic [[Utilisateur:Clopeau|Clopeau]] ([[Discussion utilisateur:Clopeau|discussion]]) 17 mars 2026 à 08:20 (CET) :Bonjour, quand je regarde ''[[Essai pour un modèle de psychisme objectif]]'', sa complétude permettrait en effet de le sortir des ébauches, mais il semble plutôt d'agir d'un travail de recherche que d'un livre pédagogique sur un sujet sourcé comme reconnu. :Donc en vertu de [[Wikilivres:Principes fondateurs|notre charte]], je propose de le déplacer sur {{WV|Recherche:Accueil}}. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 18 mars 2026 à 09:06 (CET) == Don à wikipédia depuis wikilivres == {{BlocCitation|La Wikimedia Foundation est l’organisation à but non lucratif qui soutient Wikipédia, '''les autres sites de connaissance libre de Wikimédia''', et sa mission de connaissance libre pour tous.|auteur=[https://wikimediafoundation.org/fr/give/donor-frequently-asked-questions/ FAQ], {{g|Qu'est-ce-que la Wikimedia Foundation ?}}}} Le lien de donation https://donate.wikimedia.org/w/index.php?title=Special:LandingPage&country=FR&uselang=fr&wmf_medium=sidebar&wmf_source=donate&wmf_campaign=fr.wikibooks.org '''ne parle que de wikipédia'''. '''Où sont passés les autres projets ?''' Vont-ils être fermés sauvagement par la fondation comme Wikinews récemment ? La suite au prochain épisode d{{'}}''Highlander-Wikimedia'' : {{g|À la fin, un seul ''projet'' survivra.}} ... &nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 12 mai 2026 à 11:26 (CEST) :Bonjour, :Je pense personnellement que les wikis ont des problèmes. :Il y a une vingtaine d'années, il représentait l'innovation la plus ambitieuse. :Aujourd'hui, les wikis me semblent dépassés de tous les côtés. :           wiki = Erreurs 404 ("Page introuvable") :Problèmes: :* 568 livres déclarés. 50 livres terminés (combien sont obsolètes) :* 21 527 pages déclarées. 20 000 pages orphelines. (Papa t'est où?) :Solution: :* Où sont les cours sur YouTube qui utilisent un wikibook comme référence? :Voilà mon idée sur les wiki. :* https://fr.wikiversity.org/wiki/Facult%C3%A9:Droit :* https://fr.wikiversity.org/wiki/D%C3%A9partement:Introduction_au_droit :<nowiki>~~~~</nowiki> [[Utilisateur:Xhungab|Xhungab]] ([[Discussion utilisateur:Xhungab|discussion]]) 12 mai 2026 à 13:50 (CEST) ::Ce serait intéressant de mentionner tous les projets actifs oui. [[Utilisateur:Fourmidable|Fourmidable]] ([[Discussion utilisateur:Fourmidable|discussion]]) 12 mai 2026 à 16:23 (CEST) ::: En même temps, le message est plutôt franc étant donné que l'argent des dons n'est pas investi dans le développement de Wikilivres (cf [[:meta:Community Wishlist Survey]] et les réponses à nos tickets Phabricator). ::: Par ailleurs, concernant la qualité des contenus, cela a toujours était un sujet depuis le début, et nous disposons tout de même de plusieurs pages spéciales dans [[Wikilivres:Maintenance]] pour y remédier afin que le lecteur n'ait pas la sensation de se faire empapaouter en se retrouvant sur des ébauches bien placées dans Google. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 13 mai 2026 à 08:48 (CEST) == La catégorie SI est pleine ! == Bonjour tous, Pour info, la [[:Catégorie:Suppressions immédiates demandées]] contient 9 pages. Wikilivresquement, [[Utilisateur:Fourmidable|Fourmidable]] ([[Discussion utilisateur:Fourmidable|discussion]]) 14 juin 2026 à 15:36 (CEST) :Elle est maintenant vide, merci à ceux qui s'y sont collés ! {{Sourire}} [[Utilisateur:Fourmidable|Fourmidable]] ([[Discussion utilisateur:Fourmidable|discussion]]) 24 juin 2026 à 17:17 (CEST) 9ckw626gzhlgkoslirc8z3p8mf2pqgb Sur le chemin de Messon, un modèle photo 0 83448 768582 758621 2026-06-25T09:03:20Z Xhungab 23827 768582 wikitext text/x-wiki {{Suppression Immédiate|Elle est laissée à l'abandon depuis : 19 janvier 2026 à 15:01 Tom28800. Sans contenu pertinent}} == Introduction == Ce livre présente le parcours d’un modèle photo à travers une expérience réelle et progressive dans le monde de la photographie artistique. Il a pour objectif de partager une histoire vécue tout en transmettant des connaissances utiles sur la pratique du modèle photo, les relations professionnelles avec les photographes et le cheminement personnel et artistique. À travers les différents chapitres, le lecteur découvrira comment se construit un parcours, quelles sont les étapes importantes, les apprentissages essentiels et les difficultés rencontrées. Ce livre s’adresse aussi bien aux débutants qu’aux passionnés de photographie souhaitant mieux comprendre le rôle du modèle photo et les réalités de ce milieu. L’introduction constitue une porte d’entrée vers un récit à la fois narratif et pédagogique, conçu pour accompagner le lecteur dans la découverte de ce métier et de cet univers créatif. {{AutoCat}} 5y6rq2e3de2rn3q6toootesldo5e15i Dictionnaire de philosophie/René Descartes 0 83728 768590 768381 2026-06-25T10:06:46Z PandaMystique 119061 768590 wikitext text/x-wiki {{DicoPhilo|René Descartes|lecture=oui}} La pensée de René Descartes constitue un tournant majeur dans l'histoire de la philosophie occidentale. Son ambition est de refonder l'édifice entier du savoir sur des bases inébranlables, en soumettant à l'examen de la raison toutes les autorités héritées et en cherchant, dans les opérations mêmes de l'esprit, les conditions de la certitude. == Vie et œuvres == René Descartes naît le 31 mars 1596 à La Haye, en Touraine, dans une famille de petite noblesse. Envoyé vers 1606 au collège jésuite de La Flèche, alors qu'il a une dizaine d'années, il y reçoit, jusqu'en 1614 ou 1615, un enseignement encyclopédique qui le confronte à la philosophie scolastique, aux mathématiques, à la physique et aux lettres. Il obtient ensuite, en novembre 1616, une licence en droit à l'université de Poitiers. En 1618, il gagne les Provinces-Unies et s'engage dans l'armée de Maurice de Nassau, prince d'Orange. C'est là, à Breda, qu'il fait au mois de novembre la rencontre d'Isaac Beeckman, un savant néerlandais de sept ans son aîné : cet épisode compte parmi les plus importants de sa formation. Beeckman ranime son intérêt pour les questions scientifiques et lui transmet le programme d'une « physico-mathématique », c'est-à-dire l'application exacte des mathématiques aux problèmes de la physique, qui orientera durablement ses recherches ; Descartes lui dédie son ''Compendium musicae'' à la fin de l'année. Il passe ensuite, l'année suivante, au service du duc Maximilien de Bavière, voyageant à travers l'Europe. Dans la nuit du 10 au 11 novembre 1619, alors qu'il hiverne dans le sud de l'Allemagne, il rapporte avoir eu trois songes qui lui révèlent sa vocation : fonder une science universelle sur des bases entièrement nouvelles<ref>Ces songes ne nous sont pas connus directement par Descartes lui-même, qui les évoque seulement de manière allusive dans le ''Discours de la méthode'' (II, AT VI, 11), mais par le récit qu'en a transcrit son biographe Adrien Baillet, ''La Vie de Monsieur Des-Cartes'', Paris, 1691, à partir d'un cahier perdu intitulé ''Olympica''.</ref>. Installé en Hollande à partir de 1628 pour y jouir de la [[Manuel de terminale de philosophie/Liberté|liberté]] intellectuelle, Descartes consacre les deux décennies suivantes à l'élaboration de son système philosophique. Il rédige d'abord, vraisemblablement entre 1626 et 1628, les ''Regulae ad directionem ingenii'' ([[s:Règles pour la direction de l’esprit|Règles pour la direction de l'esprit]]), qu'il laisse inachevées et qui ne paraîtront qu'après sa mort. Il rédige ensuite ''Le Monde ou Traité de la lumière'', qu'il renonce à publier après la condamnation de Galilée en 1633. En 1637 paraît le ''[[s:Discours de la méthode|Discours de la méthode]]'', suivi de trois essais scientifiques (''La Dioptrique'', ''Les Météores'', ''La Géométrie''). Les ''[[Méditations métaphysiques|Meditationes de prima philosophia]]'' paraissent en 1641 à Paris, accompagnées de six séries d'objections et de réponses ; une septième série sera ajoutée à la seconde édition de 1642, publiée à Amsterdam. Le titre de la première édition annonçait que l'ouvrage démontrait « l'existence de Dieu et l'immortalité de l'âme » ; celui de la seconde l'a remplacé par « l'existence de Dieu et la distinction réelle de l'âme et du corps de l'homme ». Les ''Principia philosophiae'' de 1644 offrent un exposé systématique de la philosophie cartésienne ; des six parties d'abord projetées, dont les deux dernières devaient traiter des vivants et de l'homme, Descartes n'en acheva que quatre, qui vont des principes de la connaissance à la cosmologie et à la physique du monde visible. Enfin, ''[[s:Les Passions de l’âme|Les Passions de l'âme]]'' paraissent en 1649, fruit d'une longue réflexion alimentée par la correspondance avec la princesse Élisabeth de Bohême. Ayant rejoint la cour de la reine Christine de Suède à Stockholm, Descartes y meurt le 11 février 1650. Dans la célèbre lettre-préface à la traduction française des ''Principes'' (1647), il compare la philosophie à un arbre dont les racines sont la [[Dictionnaire de philosophie/Métaphysique|métaphysique]], le tronc la physique et les branches les sciences particulières : la médecine, la mécanique et la [[Dictionnaire de philosophie/Morale|morale]]. Cette image exprime la conviction cartésienne que la métaphysique fonde la physique, laquelle rend à son tour possibles les sciences appliquées. Comprendre Descartes exige donc de parcourir l'ensemble de cet arbre, depuis ses racines métaphysiques jusqu'à ses prolongements pratiques. == La méthode == Le projet cartésien s'enracine dans une vive insatisfaction à l'égard du savoir hérité. Dès le ''Discours de la méthode'', Descartes fait le récit de sa formation intellectuelle pour en exposer les limites : hormis les mathématiques, aucune discipline ne lui a fourni de certitudes. La philosophie des écoles, malgré des siècles de culture, n'a produit que des controverses interminables. Le constat est sans appel : il faut reprendre les choses par le commencement et se doter d'une méthode rigoureuse capable de conduire l'esprit à la vérité. Les ''Regulae ad directionem ingenii'' constituent la première formulation de cette méthode. Descartes y identifie deux opérations premières de l'esprit : l'intuition et la déduction. L'intuition est la saisie immédiate, par un esprit attentif, d'une proposition si simple et si claire qu'aucun doute ne peut s'y glisser. La déduction est le mouvement par lequel l'esprit progresse d'une intuition à une autre, chaque étape étant elle-même évidente. Toute connaissance certaine se ramène en définitive à une chaîne d'intuitions, et la méthode consiste à organiser le travail de la pensée de manière à ne jamais rompre cette chaîne. Le ''Discours'' résume cette méthode en quatre préceptes. Le premier, dit d'évidence, commande de ne recevoir aucune chose pour vraie qu'on ne la connaisse évidemment comme telle, c'est-à-dire de n'admettre que ce qui se présente si clairement et si distinctement à l'esprit qu'il est impossible d'en douter. Le deuxième, dit d'analyse, prescrit de diviser chaque difficulté en autant de parcelles qu'il se peut, afin de la résoudre par parties. Le troisième, dit de synthèse, recommande de conduire par ordre ses pensées, en commençant par les objets les plus simples pour s'élever graduellement jusqu'aux plus composés. Le quatrième, dit de dénombrement, exige des revues si complètes qu'on soit assuré de ne rien omettre<ref>Les quatre préceptes de la méthode sont énoncés dans la deuxième partie du ''Discours de la méthode'', AT VI.</ref>. Ces quatre règles ne sont pas de simples recommandations pédagogiques : elles décrivent le fonctionnement naturel de l'intellect lorsqu'il est bien conduit, et les mathématiques en constituent le modèle par excellence. L'originalité de la méthode cartésienne réside dans sa portée universelle. Descartes ne l'envisage pas comme un instrument propre à telle ou telle discipline, mais comme le mode d'exercice de la raison elle-même. L'idée d'une ''mathesis universalis'', science générale de l'ordre et de la mesure, traduit cette ambition : toute question susceptible d'être formulée en termes de rapports et de proportions peut être résolue selon les mêmes procédures. ''La Géométrie'' de 1637, qui pose les fondements de la géométrie analytique en traduisant les figures spatiales en équations algébriques, en fournit la démonstration la plus éclatante. Il convient toutefois d'éviter de réduire à un dispositif unique les trois principaux textes méthodologiques de Descartes. Les ''Regulae'' développent une théorie de la connaissance fondée sur les natures simples et la ''mathesis universalis'', dans un horizon encore largement scientifique. Le ''Discours de la méthode'' offre, sur le mode de l'autobiographie intellectuelle, un exposé condensé et populaire destiné à un large public cultivé. Les ''Méditations'' déploient enfin une démarche métaphysique qui n'est plus strictement « méthodique » au sens des ''Regulae'', mais qui suit ce que Descartes lui-même appelle l'« ordre des raisons ». L'unité de la pensée cartésienne se manifeste dans la continuité d'un projet, non dans une stricte identité de procédure entre ces trois œuvres. Descartes reconnaîtra d'ailleurs que la méthode, si puissante soit-elle dans le domaine des sciences, ne suffit pas à elle seule à garantir la vérité de nos connaissances sur le monde : cette garantie exige un fondement d'un autre ordre, celui de la métaphysique. == La physique et la science de la nature == La physique cartésienne se construit en rupture déclarée avec la physique d'inspiration aristotélicienne telle qu'elle était enseignée dans les universités depuis le Moyen Âge. Descartes expose sa conception de la nature dans ''Le Monde'' (composé vers 1633, paru en 1664), dans la cinquième partie du ''Discours'' et dans les ''Principes de la philosophie''. Le point de départ est une critique serrée de la connaissance sensible. Contre la conviction ordinaire selon laquelle les qualités perçues (couleurs, sons, odeurs, saveurs, chaleur, froid) existent réellement dans les objets extérieurs, Descartes soutient que ces qualités ne sont que des modifications de notre esprit, produites par le mouvement de la matière sur nos organes sensoriels. De même que les mots du langage n'ont aucune ressemblance avec les choses qu'ils signifient, les sensations n'ont aucune ressemblance avec les propriétés réelles des corps qui les causent. Si les sens ne nous livrent pas l'essence des choses, seules les idées claires et distinctes de l'entendement, dont les mathématiques offrent le modèle achevé, peuvent nous faire connaître l'essence des corps. La matière, dépouillée de toutes les qualités sensibles, se réduit à l'étendue en longueur, largeur et profondeur : la substance corporelle n'est rien d'autre que l'étendue. Cette identification de la matière et de l'étendue entraîne plusieurs conséquences importantes. Elle exclut d'abord le vide : puisque la matière est l'étendue elle-même, il ne saurait y avoir d'étendue sans matière, et l'espace vide est une contradiction dans les termes. Elle implique ensuite la divisibilité indéfinie de la matière : l'étendue étant divisible à l'infini, la matière l'est aussi, et il n'existe pas d'atomes. Elle réduit enfin tous les phénomènes naturels au mouvement local de parties de matière étendue : il n'y a dans la nature que des figures et des mouvements. Dans ''Le Monde'', Descartes procède par une fiction ingénieuse : il invite le lecteur à imaginer la genèse d'un monde nouveau à partir d'une matière indifférenciée. Dieu crée la matière, la divise en parties et leur communique des mouvements régis par trois lois. La première loi pose que chaque partie de matière conserve son état de repos ou de mouvement tant qu'aucune cause extérieure ne vient le modifier : Descartes propose là l'une des premières formulations générales du principe d'inertie, dont cette conservation de l'état ne constitue toutefois qu'un premier aspect. Cette formulation s'inscrit dans un mouvement de réflexion plus large amorcé par Galilée et Isaac Beeckman au début du {{s|XVII}}, et Descartes en hérite autant qu'il le prolonge ; mais il lui confère une portée systématique nouvelle en l'inscrivant dans un cadre métaphysique d'ensemble. La deuxième loi établit que, dans le choc de deux corps, une certaine quantité de mouvement se conserve. Il faut noter que la « quantité de mouvement » cartésienne, calculée comme produit de la grandeur d'un corps par sa vitesse scalaire, n'est pas encore la grandeur vectorielle (impulsion) que la mécanique classique post-newtonienne fera intervenir : c'est en partie pour cette raison que les lois cartésiennes du choc se révéleront empiriquement inexactes. La troisième loi stipule que tout mouvement tend à se poursuivre en ligne droite, le mouvement rectiligne étant le seul qui se laisse déterminer en un instant ; c'est cette précision du caractère rectiligne qui achève l'énoncé cartésien de l'inertie. À partir de ces trois lois et des seuls chocs entre les parties de matière, Descartes prétend rendre compte de la formation des astres, des planètes, de la lumière et de l'ensemble des phénomènes naturels, par le biais d'un mécanisme universel de tourbillons de matière subtile. Descartes fonde ces lois sur l'immuabilité de Dieu : Dieu conserve dans le monde la même quantité de mouvement qu'il y a placée initialement, car il serait contraire à sa perfection d'en changer. Les principes les plus généraux de la physique sont ainsi rattachés à la métaphysique. Il faut cependant se garder de présenter la physique cartésienne comme entièrement déductive : si les lois générales du mouvement sont rattachées à l'immutabilité divine, les explications particulières des phénomènes naturels reposent en fait sur la construction de modèles mécaniques, la formulation d'hypothèses et le recours méthodique à l'expérience. Descartes récuse toute explication faisant appel aux causes finales : chercher à quoi sert un phénomène naturel, c'est prétendre connaître les desseins de Dieu, prétention qu'il juge aussi vaine qu'orgueilleuse. Il refuse également les formes substantielles et les qualités occultes de la physique scolastique, ne reconnaissant d'autres principes explicatifs que la matière étendue et le mouvement. L'expérimentation, dans ce cadre, n'a pas pour fonction de découvrir les principes premiers de la physique, qui sont rattachés à la métaphysique, mais de choisir entre plusieurs hypothèses mécaniques également compatibles avec ces principes. La physique cartésienne s'étend au domaine du vivant par une physiologie mécaniste. Dans la cinquième partie du ''Discours'' et dans le ''Traité de l'homme'' (publié ''post mortem''), Descartes décrit le corps humain comme une machine hydraulique d'une extrême complexité. La circulation du sang, la digestion, la respiration, le sommeil, les mouvements réflexes ainsi que l'exécution corporelle des mouvements s'expliquent par le seul jeu mécanique des organes, des nerfs, du sang et des esprits animaux (particules très fines du sang acheminées au cerveau par les artères). Les mouvements proprement volontaires, en revanche, supposent l'intervention de la volonté de l'âme, qui agit sur le corps dans le cadre de l'union substantielle. Nul besoin, par ailleurs, d'invoquer une âme végétative ou une âme sensible, comme le faisait la tradition aristotélicienne : la vie organique fonctionne comme un automate. De cette analyse découle la thèse, célèbre et controversée, de l'animal-machine : les animaux, dépourvus de raison et d'un langage propre à exprimer des idées, peuvent être considérés comme des automates. Il convient toutefois de noter que Descartes lui-même, dans une lettre à Henry More du 5 février 1649<ref>Lettre à More, 5 février 1649, AT V, p. 276-277 : Descartes y précise que, s'il tient pour démontré qu'on ne peut prouver l'existence d'une pensée chez les bêtes, il ne croit pas pour autant qu'on puisse démontrer qu'elles n'en ont aucune, l'esprit humain ne pénétrant pas dans leur vie intime.</ref>, reconnaît qu'on ne peut démontrer qu'il n'y a pas de pensée chez les bêtes ; ce qu'il nie n'est pas la vie ni la réactivité organique aux stimuli, mais la pensée au sens strict, c'est-à-dire le pouvoir réflexif et linguistique. L'âme, lorsqu'elle est présente chez l'homme, n'est pas principe de vie mais principe de pensée : elle apporte la conscience, la réflexion, le langage articulé. == La métaphysique : le projet fondateur == Si la physique cartésienne décrit la nature du monde au moyen d'idées claires et distinctes, encore faut-il établir que ces idées sont véridiques, c'est-à-dire qu'elles décrivent effectivement la réalité telle qu'elle est. La méthode, à elle seule, ne peut fournir cette garantie ultime. C'est à la métaphysique qu'il revient de fonder la physique en démontrant deux thèses capitales : d'une part, l'existence et la véracité de Dieu ; d'autre part, la distinction réelle de l'esprit et du corps. La première thèse est nécessaire pour garantir la vérité des idées claires et distinctes ; la seconde permet de justifier la connaissance indépendante de l'expérience sensible. Descartes expose sa métaphysique principalement dans la quatrième partie du ''Discours de la méthode'', dans les ''Méditations métaphysiques'' (1641) et dans la première partie des ''Principes''. C'est toutefois dans les ''Méditations'' que l'argumentation atteint son plus haut degré de rigueur et de pénétration. L'ouvrage se présente comme un exercice de pensée que le lecteur est invité à pratiquer avec l'auteur, une expérience intérieure de la raison progressant depuis l'incertitude la plus complète jusqu'à la certitude la plus ferme. === Le doute méthodique === La première ''Méditation'' met en œuvre un doute systématique, volontaire et hyperbolique, dont la finalité n'est pas sceptique mais fondatrice : il s'agit de suspendre l'assentiment à toutes les opinions reçues pour ne retenir que ce qui résiste à l'épreuve du doute le plus poussé. Descartes distingue plusieurs niveaux de doute, chacun plus poussé que le précédent. Le premier niveau concerne la tromperie des sens. Nous savons par expérience que les sens nous abusent parfois : une tour carrée paraît ronde de loin, un bâton plongé dans l'eau semble brisé. Puisque les sens nous ont déjà trompés, la prudence commande de ne jamais s'y fier entièrement. Toutefois, ce doute ne touche que les perceptions de choses éloignées ou très petites ; il semble que certaines données sensibles, comme le fait que je suis ici, assis devant le feu, vêtu d'une robe de chambre, ne puissent être raisonnablement contestées. Le deuxième niveau est celui de l'argument du rêve. Il m'est arrivé souvent de rêver que j'étais ici même, assis près du feu, alors que j'étais dans mon lit. Il n'existe aucun critère parfaitement certain permettant de distinguer la veille du sommeil. Dès lors, l'existence même du monde extérieur et de mon propre corps devient incertaine. Cependant, même dans le rêve, les vérités mathématiques et les natures simples (étendue, figure, nombre) semblent subsister : que je dorme ou que je veille, deux et trois font cinq et le carré n'a que quatre côtés. Le troisième niveau, le plus poussé, est celui de l'hypothèse du Dieu trompeur, transformée ensuite en fiction du malin génie. Si Dieu est tout-puissant, rien n'empêche qu'il ait pu me donner une nature telle que je me trompe même dans les vérités qui me paraissent les plus évidentes. Les vérités mathématiques elles-mêmes tombent sous le coup de ce doute. Pour donner à cette hypothèse toute sa force, Descartes la reformule sous la figure d'un malin génie, aussi puissant que rusé, qui emploierait toute son industrie à me tromper. Ce doute hyperbolique, en suspendant toute certitude, crée les conditions d'un recommencement entier de la pensée. L'hypothèse du Dieu trompeur s'inscrit dans un contexte théologique précis. La philosophie scolastique avait longuement discuté la question de savoir si la toute-puissance divine était compatible avec la vérité de la connaissance humaine, et de nombreux théologiens avaient conclu que le savoir humain ne pouvait jamais être que provisoire et conjectural face à l'infinité divine. C'est contre cette résignation épistémologique que Descartes construit sa métaphysique. === Le ''cogito'' et la nature du sujet pensant === Au cœur du doute le plus poussé surgit une certitude inébranlable. Alors même que je suppose qu'un être tout-puissant s'emploie à me tromper en toutes choses, il est impossible que je n'existe pas au moment même où je pense être trompé. Pour me tromper, il faut bien que je sois. Cette première vérité reçoit chez Descartes plusieurs formulations selon les ouvrages : « je pense, donc je suis » dans la quatrième partie du ''Discours de la méthode'', « je suis, j'existe » (''ego sum, ego existo'') dans la deuxième ''Méditation'', où la formule canonique de la conclusion est en réalité l'affirmation simple de l'existence à chaque fois que je la prononce ou la conçois, et « je pense, donc je suis » (''ego cogito, ergo sum'') dans les ''Principes de la philosophie''. C'est cette dernière formule que la tradition désignera comme « le ''cogito'' », expression elle-même postérieure à Descartes. Quel que soit le libellé, il s'agit du roc sur lequel s'édifiera tout l'édifice du savoir. Le statut logique exact de cette proposition a suscité d'amples discussions parmi les commentateurs : s'agit-il d'un syllogisme (qui supposerait une prémisse majeure « tout ce qui pense existe »), ou d'une intuition immédiate par laquelle la pensée se saisit elle-même dans l'exercice même de son doute ? Descartes lui-même, dans les ''Secondes Réponses'', insiste sur le caractère non syllogistique de cette certitude : la conclusion « j'existe » est aperçue « par une simple inspection de l'esprit ». Sa vérité est établie par le fait que toute tentative de la nier la confirme : si je pense que je n'existe pas, j'existe ; si je suis trompé, j'existe. Du ''cogito'', Descartes tire aussitôt un enseignement sur la nature du moi. La certitude d'exister ne dépend que de la pensée : si je cessais de penser, rien ne m'assurerait plus de mon existence. En revanche, je puis concevoir que je n'ai pas de corps tout en restant certain d'exister comme être pensant. La pensée, prise dans toute l'étendue de ses modalités (douter, concevoir, affirmer, nier, vouloir, refuser, imaginer, sentir), constitue donc l'attribut essentiel du moi. Le moi est une « chose qui pense » (''res cogitans''), une substance dont toute l'essence consiste dans la pensée. Cette détermination du moi par la seule pensée, indépendamment de toute référence au corps, marque une rupture nette avec la tradition aristotélicienne, pour laquelle la connaissance de l'esprit dérivait de sa réflexion sur les objets corporels. Descartes illustre cette thèse par la célèbre analyse du morceau de cire, dans la deuxième ''Méditation''<ref>L'analyse du morceau de cire se trouve dans la deuxième ''Méditation'', AT VII ; trad. fr. AT IX-1.</ref>. Un morceau de cire fraîchement tiré de la ruche possède une couleur, une odeur, une figure, une taille déterminées. Si on l'approche du feu, toutes ces qualités sensibles changent : la couleur disparaît, l'odeur s'évanouit, la figure se modifie. Pourtant, nous jugeons que c'est toujours le même morceau de cire. Ce jugement d'identité ne peut, selon Descartes, venir des sens, puisque toutes les qualités sensibles ont changé. Il résulte d'un acte de l'entendement, qui saisit la cire comme une substance étendue, flexible et muable, propriétés que l'imagination ne peut embrasser dans leur infinité mais que l'esprit seul conçoit. Ainsi, même la connaissance des corps suppose l'intervention de l'entendement et de ses idées, et non la seule réception passive des données sensibles. Le doute et le ''cogito'' ont donc conjointement établi deux résultats : l'existence certaine du moi pensant et la primauté de la connaissance intellectuelle sur la connaissance sensible. Reste à savoir si cette certitude du moi peut s'étendre à d'autres réalités, et en premier lieu à Dieu. === Les preuves de l'existence de Dieu === Le ''cogito'' fournit un premier fondement, mais il ne suffit pas à garantir la vérité de toutes nos idées claires et distinctes, car celles-ci restent menacées par l'hypothèse du Dieu trompeur. Pour lever cette menace, il faut démontrer que Dieu existe et qu'il n'est pas trompeur. Descartes propose trois preuves de l'existence de Dieu dans les troisième et cinquième ''Méditations''<ref>Les preuves a posteriori, par l'idée d'infini et par la cause du sujet pensant, occupent la troisième ''Méditation'' ; la preuve ontologique se déploie dans la cinquième. AT VII ; trad. fr. AT IX-1.</ref>. La première preuve, dite preuve par l'effet, part de l'idée de Dieu que l'esprit trouve en lui-même. Descartes distingue dans toute idée deux aspects : sa réalité formelle, c'est-à-dire son être en tant que mode de la pensée, et sa réalité objective, c'est-à-dire le contenu qu'elle représente. Or, la réalité objective d'une idée doit avoir une cause qui possède au moins autant de réalité formelle. L'idée de Dieu est l'idée d'une substance infinie, éternelle, immuable, toute-puissante, omnisciente et créatrice de toutes choses. La réalité objective de cette idée excède de manière incommensurable la réalité formelle du moi, substance finie et imparfaite. Le moi ne peut donc pas être la cause de l'idée de Dieu : seul un être effectivement infini et parfait peut avoir produit en nous cette idée. Donc Dieu existe. Cette preuve suppose que l'idée d'infini est positive et originaire, et non simplement dérivée de la négation du fini. Descartes s'oppose ici à la position thomiste : loin que l'idée d'infini résulte de la négation des limites du fini, c'est au contraire l'idée du fini qui suppose celle de l'infini, car je ne pourrais pas me reconnaître fini et imparfait si je n'avais en moi l'idée d'un être infini et parfait par comparaison auquel je me juge déficient. La deuxième preuve, développée également dans la troisième ''Méditation'', cherche la cause de l'existence du moi en tant qu'il possède l'idée de Dieu. Je n'ai pas pu me créer moi-même, car si j'avais eu ce pouvoir, je me serais donné toutes les perfections dont j'ai l'idée. Aucune cause moins parfaite que Dieu ne peut être la cause ultime de mon existence : la régression à l'infini étant impossible, il faut nécessairement aboutir à un être qui a en soi le principe de son existence et qui possède toutes les perfections contenues dans l'idée de Dieu. La troisième preuve, exposée dans la cinquième ''Méditation'', est la preuve ontologique. Elle procède de la seule définition de Dieu comme être souverainement parfait. Tout comme certaines propriétés appartiennent nécessairement à l'essence d'une figure géométrique (les trois angles d'un triangle valent ensemble deux droits), l'existence nécessaire appartient à l'essence d'un être souverainement parfait. Il serait donc contradictoire de concevoir un tel être auquel manquerait l'existence : sa nature même implique qu'il existe. L'existence appartient ainsi à l'essence de Dieu, et nier son existence reviendrait à se contredire. Kant qualifiera plus tard cet argument de « preuve ontologique », et lui reprochera de traiter l'existence comme un prédicat susceptible d'enrichir le concept d'une chose, alors que, selon lui, l'existence n'ajoute rien au contenu d'un concept. L'objection kantienne ne rend toutefois pas pleinement compte du raisonnement cartésien, qui n'ajoute pas l'existence à une liste de perfections : il infère sa nécessité de la considération de l'essence divine prise en elle-même. === La véracité divine et le problème du cercle === L'existence de Dieu une fois établie, Descartes en déduit que Dieu, être souverainement parfait, ne saurait être trompeur, car la tromperie suppose un défaut incompatible avec la perfection divine. Si Dieu n'est pas trompeur, alors la lumière naturelle qu'il a mise en nous, c'est-à-dire notre faculté de connaître les choses clairement et distinctement, est fiable. Tout ce que nous concevons clairement et distinctement est vrai. La menace du malin génie est définitivement écartée, et la science humaine reçoit son fondement ultime dans la véracité divine. On a objecté à Descartes, dès la parution des ''Méditations'', que son raisonnement serait circulaire : il invoque Dieu pour garantir la vérité des idées claires et distinctes, mais il démontre l'existence de Dieu au moyen d'idées claires et distinctes<ref>L'objection est formulée notamment par Antoine Arnauld dans les ''Quatrièmes Objections'' ; Descartes y répond dans les ''Secondes Réponses'' et les ''Quatrièmes Réponses''. AT VII ; trad. fr. AT IX-1.</ref>. Ce « cercle cartésien » a suscité d'innombrables commentaires, et les interprètes ne s'accordent pas sur la portée exacte de la réponse de Descartes. Celui-ci distingue entre les intuitions actuelles, dont l'esprit ne peut douter au moment même où il les perçoit, et les résultats de démonstrations passées, dont on pourrait douter si l'on n'avait pas la garantie divine. La véracité de Dieu ne serait donc pas requise pour assurer les évidences présentes, celles-ci s'imposant d'elles-mêmes, mais pour garantir que ce qui a été clairement et distinctement perçu reste vrai même lorsque l'esprit ne se le représente plus actuellement. La garantie divine porterait ainsi sur la mémoire de nos évidences et sur la fiabilité de nos raisonnements longs et complexes. == La théorie des idées == La métaphysique cartésienne repose sur une théorie des idées qui reprend certains thèmes de la tradition platonicienne, en particulier la thèse selon laquelle les connaissances les plus hautes ne proviennent pas des sens. Descartes n'adopte pas pour autant le cadre métaphysique de Platon (il ne postule ni un monde séparé des Formes, ni la réminiscence), mais il partage avec lui la conviction que l'expérience sensible ne fournit pas, à elle seule, de connaissance certaine. Descartes distingue trois sortes d'idées selon leur origine. Les idées adventices semblent provenir de l'expérience extérieure : ce sont les sensations de couleur, de son, de chaleur, etc. Les idées factices sont fabriquées par l'imagination, comme l'idée de sirène ou de chimère. Les idées innées, enfin, se trouvent en nous indépendamment de toute expérience sensible : ce sont les idées de Dieu, de l'âme, de l'étendue, des figures géométriques, des vérités mathématiques et logiques. Dans les ''Notae in programma'' (1648), réponse au placard de Regius, Descartes précise que les idées innées ne sont pas des contenus présents à l'esprit sous une forme toute faite, mais des dispositions ou des aptitudes que l'esprit possède de produire ces idées par sa propre puissance. L'innéité ne désigne donc pas une présence actuelle de toutes les essences dans la conscience, mais la faculté naturelle de l'esprit de former ces idées sans aide extérieure. Les idées innées se distinguent des autres par plusieurs caractéristiques. Elles ne sont pas imposées par un objet extérieur, mais produites par la puissance propre de l'entendement ; leur contenu, en revanche, s'impose à l'esprit avec nécessité et ne peut être modifié par la volonté. L'esprit est passif devant ce contenu : les vérités mathématiques ne sont pas des inventions mais des découvertes, comme l'atteste le fait que les progrès mathématiques se font pas à pas, chaque nouvelle vérité s'imposant à l'esprit avec une nécessité que celui-ci ne contrôle pas. Cette nécessité interne du contenu indique, selon Descartes, qu'il y a là plus qu'une simple production subjective : les idées innées correspondent à ce qu'il appelle, dans la cinquième ''Méditation'', des « vraies et immuables natures », objets stables auxquels la pensée se rapporte. Ces natures ne forment cependant pas, chez Descartes, un domaine d'essences entièrement indépendantes au sens où l'auraient compris certains platoniciens : ainsi que le précise la doctrine de la libre création des vérités éternelles, elles dépendent entièrement de la volonté divine qui les a librement instituées. L'innéisme cartésien doit donc se comprendre dans cet équilibre subtil : objectivité réelle des idées et des essences qu'elles représentent, garantie par la véracité divine ; absence d'autonomie ontologique de ces essences à l'égard de leur cause créatrice. La théorie des idées innées, associée à la véracité divine, constitue le fondement de l'anti-empirisme cartésien. La connaissance authentique du monde ne passe pas par les sens, mais par l'entendement pur et ses idées innées. Les idées adventices, quant à elles, ne nous informent pas sur la nature réelle des choses, mais elles remplissent une autre fonction : elles incitent irrésistiblement l'esprit à croire à l'existence d'un monde extérieur. Si les corps n'existaient pas, Dieu serait trompeur, puisqu'il nous a naturellement portés à croire à leur existence sans nous donner aucun moyen de corriger cette croyance. La véracité divine garantit donc l'existence du monde extérieur, mais c'est par les idées innées que nous en connaissons la nature. Il faut souligner le caractère paradoxal de cet ordre : Descartes connaît la nature du monde (l'étendue géométrique) avant d'en avoir établi l'existence réelle. == La théorie de la libre création des vérités éternelles == Une dimension essentielle de la métaphysique cartésienne tient à la doctrine de la libre création des vérités éternelles. Formulée pour la première fois dans la lettre à Mersenne du 15 avril 1630<ref>Lettre à Mersenne du 15 avril 1630, AT I, p. 145 : « Les vérités mathématiques, lesquelles vous nommez éternelles, ont été établies de Dieu et en dépendent entièrement, aussi bien que tout le reste des créatures. » Voir aussi les lettres du 6 mai et du 27 mai 1630, AT I, p. 149-153.</ref>, cette doctrine affirme que les vérités éternelles (y compris les vérités mathématiques et les lois logiques) ont été librement instituées par Dieu. Dieu n'est pas soumis aux vérités éternelles comme à des contraintes extérieures ; c'est lui qui les a établies par un acte de volonté libre. Que deux et trois fassent cinq, que le tout soit plus grand que la partie, que les contradictoires ne puissent être vraies ensemble : tout cela ne résulte pas d'une nécessité que Dieu subirait, mais d'un décret divin. Dieu aurait pu en décider autrement ; mais notre esprit fini est incapable de se représenter positivement en quoi consisterait un tel ordre. Reconnaître que les vérités nécessaires dépendent de la volonté divine n'est donc pas se donner le pouvoir de concevoir une autre logique : c'est seulement admettre que leur nécessité même est instituée, et qu'elle excède notre compréhension. Cette thèse, d'une grande audace, porte à son comble l'affirmation de la toute-puissance divine. Sans être entièrement nouvelle (la tradition scotiste avait, à sa manière, déjà étendu la liberté divine très loin), elle revêt chez Descartes une portée nouvelle et une intensité particulière. Sa portée exacte et son articulation avec le reste de la métaphysique cartésienne font l'objet de discussions persistantes parmi les commentateurs. Bien qu'absente des ''Méditations'' proprement dites, la doctrine n'est pas confinée à la correspondance : Descartes la formule dans les ''Cinquièmes'' et ''Sixièmes Réponses aux Objections'', ainsi que dans certaines parties des ''Principes'' (I, 22-24)<ref>''Principes de la philosophie'', I, art. 22-24, AT VIII-1, p. 13-14 ; AT IX-2, p. 36-37. Sur cette doctrine, voir Jean-Luc Marion, ''Sur la théologie blanche de Descartes'', Paris, PUF, 1981.</ref>. Elle n'est cependant pas explicitement mobilisée dans la construction des preuves métaphysiques de l'existence de Dieu. == L'erreur et le libre arbitre == Si Dieu est vérace et a doté l'homme d'une faculté de connaître fiable, comment se fait-il que celui-ci se trompe ? La quatrième ''Méditation'' est consacrée à cette question. L'erreur résulte du concours de deux facultés : l'entendement, qui est fini, et la volonté, qui est infinie. L'entendement ne conçoit qu'un nombre limité d'idées claires et distinctes. La volonté, en revanche, s'étend à tout : elle peut affirmer ou nier au-delà de ce que l'entendement lui présente clairement. L'erreur survient lorsque la volonté se prononce sur des matières que l'entendement ne conçoit pas clairement et distinctement. Elle n'est donc pas imputable à Dieu, mais au mauvais usage que l'homme fait de sa liberté. Dieu a créé l'homme avec un entendement fini et une volonté infinie, et cette disproportion n'est pas un défaut mais le signe de la grandeur de l'homme : c'est par la volonté, qui est selon Descartes « la plus grande perfection de l'homme », que nous sommes faits à l'image de Dieu. La liberté humaine occupe ainsi une place centrale dans la métaphysique cartésienne. Descartes la conçoit comme le pouvoir de choisir entre des contraires, mais aussi et surtout comme la capacité de se déterminer par la seule lumière de l'entendement. La plus haute forme de liberté n'est pas l'indifférence, état dans lequel la volonté ne penche vers aucun côté, mais la détermination éclairée par laquelle l'esprit adhère au vrai et au bien qu'il connaît clairement. Plus l'entendement voit clairement, plus la volonté s'y porte infailliblement, et plus la liberté est grande. Ce double visage de la liberté cartésienne, qui oppose liberté d'indifférence et liberté éclairée, fera l'objet de discussions importantes dans la postérité du cartésianisme. == Le dualisme : distinction et union de l'âme et du corps == La sixième ''Méditation'' établit la distinction réelle de l'âme et du corps. Puisque l'esprit peut être conçu clairement et distinctement sans le corps, et le corps sans l'esprit, et puisque Dieu peut réaliser tout ce qui est clairement et distinctement conçu, l'âme et le corps sont deux substances réellement distinctes, capables d'exister séparément. L'âme est une substance pensante (''res cogitans''), dont l'essence est la pensée ; le corps est une substance étendue (''res extensa''), dont l'essence est l'étendue en trois dimensions. Ce dualisme des substances est l'une des thèses fondatrices de la philosophie cartésienne : il rend intelligible l'idée que l'esprit possède des ressources propres qui ne dérivent pas de l'expérience sensible (l'âme, étant indépendante du corps, peut posséder des idées qui ne proviennent pas de l'expérience corporelle), justifie la physique mécaniste (les corps, n'étant qu'étendue, n'ont besoin pour être expliqués que de figures et de mouvements) et prépare la thèse de l'immortalité de l'âme. Sur ce dernier point, il faut toutefois souligner que les ''Méditations'' n'apportent pas une démonstration directe de l'immortalité personnelle ; elles établissent la distinction réelle, dont on peut inférer que l'âme n'est pas naturellement détruite par les mêmes causes que le corps. Descartes lui-même, en réponse à des objections, présente cette distinction comme une condition de l'immortalité plutôt que comme sa preuve achevée, dont l'établissement définitif relève selon lui de la théologie. Cependant, le dualisme cartésien ne s'en tient pas à la seule distinction. L'expérience quotidienne atteste que l'âme et le corps, bien que réellement distincts, sont étroitement unis. La douleur, le plaisir, la faim, la soif ne sont pas de pures pensées détachées du corps, mais des sentiments confus résultant de l'union de l'âme et du corps. L'âme n'est pas dans le corps comme un pilote en son navire, qui percevrait les avaries du vaisseau de l'extérieur : elle lui est intimement mêlée, de sorte que les affections du corps se traduisent immédiatement en modifications de l'âme. Dans les ''Quatrièmes Réponses aux Objections'', Descartes parle d'une union qui n'est « pas accidentelle, mais substantielle ». L'expression, empruntée au vocabulaire scolastique, est cependant employée par Descartes dans un sens largement transformé : il ne s'agit plus de l'union d'une forme et d'une matière au sens aristotélicien, mais d'un fait d'expérience irréductible à la seule distinction rationnelle des substances. La princesse Élisabeth lui adressera d'ailleurs, dès 1643, des objections aiguës sur la manière dont deux substances aussi hétérogènes peuvent agir l'une sur l'autre, objections auxquelles Descartes peinera à répondre de manière entièrement satisfaisante. Descartes cherche ainsi à se frayer une voie entre les modèles platonicien et aristotélicien. Platon avait raison de soutenir que l'âme et le corps sont deux réalités distinctes, mais il n'a pas suffisamment rendu compte de leur union intime. Aristote avait raison de concevoir leur union comme celle d'une forme et d'une matière constituant un seul tout, mais il n'avait pas, aux yeux de Descartes, suffisamment reconnu leur distinction substantielle. L'originalité de la position cartésienne est de maintenir ensemble ces deux thèses apparemment contradictoires. La distinction réelle est établie par la raison ; l'union, quant à elle, est saisie par une notion primitive ''sui generis'' et par l'expérience intérieure que chacun en fait, comme l'explique Descartes dans la lettre à Élisabeth du 21 mai 1643<ref>Lettre à Élisabeth du 21 mai 1643, AT III, p. 663-668. Descartes y distingue trois sortes de notions primitives : celles qui se rapportent à l'âme seule, celles qui se rapportent au corps seul, et celle de l'union, par laquelle nous concevons que l'âme meut le corps et que le corps cause des sentiments dans l'âme.</ref>. Cette union a une finalité pratique : les sensations, bien qu'elles déforment la réalité du point de vue de la connaissance spéculative, informent correctement sur ce qui est utile ou nuisible à la conservation du composé âme-corps. L'homme se trompe non pas en ayant des sensations, mais lorsqu'il utilise ces informations pratiques à des fins de connaissance théorique. Sur la question du lieu de l'interaction entre l'âme et le corps, Descartes identifie la glande pinéale (ou ''conarium'') comme le siège principal de l'âme, non parce que l'âme y serait enfermée (l'âme étant immatérielle ne saurait être localisée comme un corps), mais parce que c'est la seule partie du cerveau qui ne soit pas double et qui puisse servir de point de convergence entre les mouvements des esprits animaux et les actes de la pensée<ref>Sur la glande pinéale comme siège des fonctions de l'âme, voir ''Les Passions de l'âme'', I, art. 31-32, AT XI, ainsi que le ''Traité de l'homme'', AT XI.</ref>. Cette hypothèse physiologique, vivement critiquée dès le vivant de Descartes, a le mérite de poser explicitement le problème de l'interaction entre deux substances hétérogènes, problème qui occupera toute la philosophie post-cartésienne. La physique cartésienne accroît encore cette difficulté : puisque la quantité de mouvement, grandeur scalaire, se conserve, on voit mal comment l'âme pourrait l'augmenter sans contrevenir à ce principe. Descartes lui-même n'affirme pas que l'âme se bornerait à modifier la direction des esprits animaux sans rien ajouter à leur mouvement ; ce sont des cartésiens postérieurs qui avancèrent cette solution, à laquelle Leibniz objecta qu'un changement de direction suppose lui aussi une modification de la quantité de mouvement et demeure donc incompatible avec sa conservation. La question du dualisme ne concerne pas seulement la métaphysique : elle engage directement la morale. Car c'est de l'union de l'âme et du corps que naissent les passions, et c'est en apprenant à régler cette union que l'homme peut espérer bien conduire sa vie. == La morale == La morale, dans l'image de l'arbre des sciences, constitue l'une des branches les plus élevées. Descartes n'a pas eu le temps de rédiger un traité de morale systématique, mais sa pensée éthique se déploie dans le ''Discours de la méthode'', dans la correspondance avec la princesse Élisabeth de Bohême et avec Pierre Chanut, et dans ''Les Passions de l'âme''. Dans la troisième partie du ''Discours'', Descartes présente une « morale par provision », destinée à guider la conduite pendant que l'esprit est occupé à reconstruire l'édifice du savoir. Cette morale provisoire comporte trois maximes principales. La première prescrit d'obéir aux lois et aux coutumes de son pays, de conserver la religion dans laquelle on a été élevé et de suivre les opinions les plus modérées. La deuxième recommande la fermeté et la résolution dans les actions : une fois qu'on a adopté une opinion, il faut la suivre avec constance, même si elle est incertaine, plutôt que de demeurer irrésolu. La troisième maxime, d'inspiration stoïcienne, conseille de changer ses désirs plutôt que l'ordre du monde, c'est-à-dire de limiter ses aspirations à ce qui est vraiment en notre pouvoir (nos pensées seules le sont), et de considérer tout le reste comme indépendant de notre volonté. La correspondance avec Élisabeth, entre 1643 et 1649, permet à Descartes de préciser et d'approfondir sa pensée morale au-delà de la morale provisoire. Le souverain bien de l'homme est la béatitude, qui ne se confond pas avec le bonheur dépendant des circonstances extérieures, mais consiste en un contentement intérieur de l'esprit résultant du bon usage de la volonté. Trois conditions sont requises pour y parvenir : connaître ce qui est réellement bien, c'est-à-dire ce que la raison nous enseigne ; avoir la ferme résolution de faire tout ce que la raison nous conseille ; limiter nos désirs à ce qui dépend de nous. Descartes ajoute à ces principes la méditation de certaines vérités essentielles : l'existence d'un Dieu dont tout dépend, la distinction de l'âme et du corps qui nous assure de la possibilité de survivre à la mort, l'immensité de l'univers qui relativise nos attachements terrestres, et le sentiment d'appartenir à une communauté humaine dont le bien doit l'emporter sur notre bien particulier. == Les passions de l'âme == Le dernier ouvrage publié du vivant de Descartes, ''Les Passions de l'âme'' (1649), est issu d'une longue réflexion alimentée par la correspondance avec la princesse Élisabeth, à qui Descartes avait envoyé en 1646 un premier traité qui constitua l'embryon de l'ouvrage publié. Il s'agit à la fois d'un traité de physiologie et d'un traité de morale. Descartes y entreprend d'étudier les passions « en physicien », c'est-à-dire de les expliquer par leurs causes naturelles et mécaniques, sans visée rhétorique ni moraliste. Les passions sont définies comme des perceptions, sentiments ou émotions de l'âme, causées et entretenues par certains mouvements des esprits animaux. Elles sont causées par le corps, mais ressenties par l'âme, et constituent l'un des effets les plus manifestes de l'union de l'âme et du corps. Descartes identifie six passions primitives, dont toutes les autres sont des espèces ou des combinaisons : l'admiration, l'amour, la haine, le désir, la joie et la tristesse. L'admiration occupe une place à part, car elle est « la première de toutes » et nous dispose à apprendre et à connaître les choses nouvelles. Les autres passions se rapportent au bien et au mal, présent ou futur. Toutes les passions, considérées en elles-mêmes, sont bonnes et utiles : elles portent l'âme à vouloir les choses que la nature lui enseigne être utiles et à persévérer dans cette volonté. Une vie sans passions serait une vie appauvrie, dépourvue de saveur et de plaisir. Descartes se distingue ici nettement du stoïcisme, qui prônait l'éradication des passions. Néanmoins, les passions ont besoin d'être réglées, car leurs excès peuvent conduire à des actions nuisibles. Descartes estime cependant que la raison ne peut pas combattre directement les passions par la seule force de ses arguments : les passions procèdent de mouvements physiologiques que seuls des mouvements contraires peuvent contrebalancer. La stratégie consiste donc à susciter des passions contraires à celles que l'on veut maîtriser, en portant l'attention sur les pensées qui leur sont ordinairement associées. L'esprit peut aussi tirer parti du caractère arbitraire de la relation entre mouvements corporels et pensées de l'âme : par l'habitude et l'exercice, il est possible de modifier les associations naturelles et d'obtenir, par exemple, que la vue d'un danger suscite la hardiesse plutôt que la peur. Ce travail sur les passions, que Descartes compare au dressage d'un animal, fait de l'homme l'artisan de sa propre nature. La vertu cardinale de la morale cartésienne est la générosité, que les ''Passions de l'âme'' présentent comme « la clé de toutes les autres vertus » (art. 161)<ref>Sur la générosité, sa définition et ses propriétés, voir ''Les Passions de l'âme'', III, art. 153, 156 et 161, AT XI.</ref>. Elle se définit par deux traits indissociables : la reconnaissance que rien ne nous appartient en propre que la libre disposition de nos volontés, et la ferme et constante résolution de bien user de ce libre arbitre. L'homme généreux ne s'estime que pour son bon usage de la liberté, ne méprise personne, n'envie rien et affronte les événements avec résolution et tranquillité. La générosité est à la fois une passion et une vertu : c'est le sentiment de notre propre liberté érigé en principe de conduite. Apparue tardivement dans la pensée cartésienne, elle réalise une synthèse originale entre plusieurs traditions éthiques : magnanimité aristotélicienne, fermeté stoïcienne, humilité chrétienne et culture épicurienne d'un plaisir épuré. Elle conduit à l'estime de soi fondée sur le bon usage du libre arbitre, et à une bienveillance universelle, puisque celui qui reconnaît que la vraie grandeur réside dans le bon usage de la volonté ne peut manquer de la reconnaître aussi chez autrui. == L'héritage cartésien == L'influence de Descartes sur la philosophie moderne est vaste et multiforme. En fondant la philosophie sur le sujet pensant, il devient l'un des points de départ majeurs de ce que l'on a pu appeler la « philosophie de la conscience », qui marque la pensée occidentale jusqu'à la fin du {{s|XIX}}. Une telle primauté du rapport de soi à soi doit toutefois être nuancée : elle connaît des antécédents augustiniens et scolastiques, et la modernité philosophique ne se réduit pas à cette seule lignée. La réception du cartésianisme commence dès le vivant de Descartes, dans des conditions souvent conflictuelles. Aux Provinces-Unies, le théologien calviniste Gisbert Voet (Voetius) attaque dès 1640 la philosophie cartésienne, soupçonnée d'athéisme, et fait condamner son enseignement à l'université d'Utrecht. La querelle se prolongera durant des années et inspirera à Descartes son ''Épître à Voetius'' (1643)<ref>Sur ces querelles, voir Theo Verbeek, ''Descartes and the Dutch : Early Reactions to Cartesian Philosophy, 1637-1650'', Carbondale, Southern Illinois University Press, 1992.</ref>. En France, l'œuvre cartésienne suscite également des controverses dans les universités, qui restent largement aristotéliciennes, et l'enseignement du cartésianisme y sera plusieurs fois entravé : en 1671 puis en 1691, des interdictions seront prononcées contre la diffusion des thèses cartésiennes dans les collèges. Les œuvres de Descartes seront en partie mises à l'Index par Rome en 1663<ref>Sur ces épisodes, voir Tad M. Schmaltz, ''Radical Cartesianism : The French Reception of Descartes'', Cambridge, Cambridge University Press, 2002. Sur la question particulière de l'eucharistie, voir Jean-Robert Armogathe, ''Theologia cartesiana : l'explication physique de l'Eucharistie chez Descartes et dom Desgabets'', La Haye, Martinus Nijhoff, 1977.</ref>. Un point particulièrement sensible concerne l'eucharistie : la réduction cartésienne de la substance corporelle à l'étendue rend difficile l'explication scolastique de la transsubstantiation, qui supposait la persistance d'accidents sans substance. Descartes, conscient du problème, propose dans les ''Quatrièmes Réponses aux Objections'' et dans la correspondance avec Mesland<ref>Lettre à Mesland du 9 février 1645, AT IV, p. 161-170.</ref> une réinterprétation du dogme à partir de sa propre métaphysique ; ces explications ne convaincront pas tous les théologiens et alimenteront la suspicion d'hétérodoxie. À Port-Royal, en revanche, le cartésianisme rencontre un accueil largement favorable. Antoine Arnauld, auteur des ''Quatrièmes Objections'' aux ''Méditations'', est l'un des premiers grands défenseurs de la philosophie cartésienne ; il en utilise les éléments dans ''La Logique ou l'Art de penser'' (1662), composée avec Pierre Nicole. Blaise Pascal, plus réservé, prend cependant ses distances à l'égard d'une métaphysique qu'il juge trop ambitieuse et trop indifférente aux questions existentielles : son célèbre jugement, « je ne puis pardonner à Descartes »<ref>Blaise Pascal, ''Pensées'', éd. Lafuma, fragment 1001 (éd. Brunschvicg, fragment 77) : « Je ne puis pardonner à Descartes : il aurait bien voulu, dans toute sa philosophie, se pouvoir passer de Dieu ; mais il n'a pu s'empêcher de lui faire donner une chiquenaude, pour mettre le monde en mouvement ; après cela, il n'a plus que faire de Dieu. »</ref>, exprime une réticence au projet cartésien d'autonomie de la raison. Sur le plan strictement philosophique, le dualisme cartésien soulève des difficultés que reprendront les principaux successeurs. La question de l'interaction entre deux substances hétérogènes occupera Malebranche, Spinoza et Leibniz. Malebranche, refusant toute action du corps sur l'âme, développe l'occasionalisme : Dieu seul est cause efficiente, et les événements corporels ne sont que l'occasion des modifications de l'âme. Spinoza, récusant le dualisme lui-même, identifie la pensée et l'étendue comme deux attributs d'une substance unique, Dieu ou la Nature. Leibniz propose l'harmonie préétablie, selon laquelle l'âme et le corps suivent chacun leurs propres lois sans interaction réelle, leurs séries d'événements étant coordonnées de toute éternité par Dieu. Sur le plan scientifique, la physique cartésienne joue un rôle de premier plan dans l'établissement du mécanisme moderne. La théorie des tourbillons offre un modèle cosmologique cohérent qui sera enseigné dans une grande partie de l'Europe pendant près d'un siècle. Mais la publication des ''Principia mathematica'' de Newton en 1687 ouvre une longue confrontation entre cartésiens et newtoniens : la physique des tourbillons, qui prétendait expliquer mécaniquement le mouvement des planètes par les chocs de la matière subtile, se voit progressivement supplantée par la mécanique newtonienne, qui introduit une attraction universelle agissant à distance. Au {{s|XVIII}}, le modèle cartésien est encore défendu en France, notamment par Bernard de Fontenelle, ainsi que dans certains milieux savants des Provinces-Unies ; mais l'autorité de Newton finit par s'imposer dans la communauté savante européenne au milieu du siècle. La géométrie analytique, en revanche, demeure l'une des contributions majeures de Descartes à l'histoire des mathématiques. La méthode du doute, la primauté accordée à la certitude du ''cogito'', la théorie des idées innées et ce que la tradition postérieure interprétera comme une distinction du sujet et de l'objet, formule rétrospective qui n'appartient pas au vocabulaire de Descartes lui-même, constituent des points de référence obligés de toute la philosophie ultérieure. Locke et Hume contestent l'innéisme en faisant fond sur l'expérience, tandis que Kant entreprend de refonder la métaphysique à partir d'une critique de la raison pure dans laquelle il reproche à Descartes son traitement de l'existence comme prédicat. Au {{s|XX}}, Husserl revendique explicitement l'héritage cartésien dans ses ''Méditations cartésiennes'' (1931), tout en transformant le ''cogito'' en sujet transcendantal de la phénoménologie. Sartre, Merleau-Ponty et plus largement la phénoménologie française entretiendront un rapport complexe à Descartes, fait à la fois de reprise et de critique. Les sciences cognitives et les philosophies analytiques de l'esprit héritent quant à elles, sur le mode souvent polémique, du problème des rapports entre l'esprit et le corps que Descartes a légué à la modernité. == Notes et références == <references /> == Bibliographie == === Éditions des œuvres de Descartes === * Charles Adam et Paul Tannery (éd.), ''Œuvres de Descartes'', 11 vol., Paris, Léopold Cerf, 1897-1913 ; nouvelle présentation augmentée, 11 vol., Paris, Vrin-CNRS, 1964-1974 (référence usuelle, citée AT suivi du tome et de la page). * Ferdinand Alquié (éd.), ''Œuvres philosophiques de Descartes'', 3 vol., Paris, Garnier, 1963-1973. * René Descartes, ''Œuvres complètes'', Paris, Gallimard, « Bibliothèque de la Pléiade », vol. I, 2009 (édition dirigée par Jean-Marie et Michelle Beyssade). === Études biographiques et générales === * Stephen Gaukroger, ''Descartes : une biographie intellectuelle'', traduit de l'anglais, Paris, PUF, 1997 (édition originale Oxford, 1995). * Geneviève Rodis-Lewis, ''Descartes. Biographie'', Paris, Calmann-Lévy, 1995. * Geneviève Rodis-Lewis, ''L'Œuvre de Descartes'', 2 vol., Paris, Vrin, 1971. * John Cottingham (dir.), ''The Cambridge Companion to Descartes'', Cambridge, Cambridge University Press, 1992. === Études sur la métaphysique et la théorie de la connaissance === * Ferdinand Alquié, ''La Découverte métaphysique de l'homme chez Descartes'', Paris, PUF, 1950. * Martial Gueroult, ''Descartes selon l'ordre des raisons'', 2 vol. (''L'âme et Dieu'' ; ''L'âme et le corps''), Paris, Aubier, 1953. * Jean-Marie Beyssade, ''La Philosophie première de Descartes'', Paris, Flammarion, 1979. * Harry Frankfurt, ''Démons, rêveurs et fous : la défense de la raison dans les Méditations de Descartes'', traduit de l'anglais, Paris, PUF, 1989 (édition originale 1970). * Jean-Luc Marion, ''Sur la théologie blanche de Descartes'', Paris, PUF, 1981. * Jean-Luc Marion, ''Questions cartésiennes'', 2 vol., Paris, PUF, 1991 et 1996. * Vincent Carraud, ''Causa sive ratio. La raison de la cause, de Suarez à Leibniz'', Paris, PUF, 2002. * Gary Hatfield, ''Descartes and the Meditations'', Londres, Routledge, 2003. * Desmond Clarke, ''Descartes's Theory of Mind'', Oxford, Oxford University Press, 2003. === Études sur la physique et la science cartésiennes === * Daniel Garber, ''La Physique métaphysique de Descartes'', traduit de l'anglais, Paris, PUF, 1999 (édition originale Chicago, 1992). * Daniel Garber, ''Descartes Embodied: Reading Cartesian Philosophy through Cartesian Science'', Cambridge, Cambridge University Press, 2001. * Desmond Clarke, ''Descartes' Philosophy of Science'', Manchester, Manchester University Press, 1982. * Stephen Gaukroger, John Schuster et John Sutton (dir.), ''Descartes' Natural Philosophy'', Londres et New York, Routledge, 2000. * A. I. Sabra, ''Theories of Light from Descartes to Newton'', Cambridge, Cambridge University Press, 1981 (première édition 1967). * Tarek R. Dika, ''Descartes's Method: The Formation of the Subject of Science'', Oxford, Oxford University Press, 2023. === Études sur la morale, l'anthropologie et les passions === * Geneviève Rodis-Lewis, ''La Morale de Descartes'', Paris, PUF, 1957. * Denis Kambouchner, ''L'Homme des passions. Commentaires sur Descartes'', 2 vol., Paris, Albin Michel, 1995. * Denis Kambouchner, ''Descartes et la philosophie morale'', Paris, Hermann, 2008. === Études sur le cartésianisme et sa réception === * Tad M. Schmaltz, ''Early Modern Cartesianisms : Dutch and French Constructions'', New York, Oxford University Press, 2017. * Steven Nadler, ''The Cambridge Companion to Malebranche'', Cambridge, Cambridge University Press, 2000. * Theo Verbeek, ''Descartes and the Dutch : Early Reactions to Cartesian Philosophy, 1637-1650'', Carbondale, Southern Illinois University Press, 1992. === Ouvrages de référence === * Frédéric de Buzon et Denis Kambouchner, ''Le Vocabulaire de Descartes'', Paris, Ellipses, 2002 (nouvelle édition 2011). * Jean-François Pradeau (dir.), ''Histoire de la philosophie'', Paris, Seuil, 2009. === Sources textuelles citées dans cet article === * ''Discours de la méthode'' (1637), parties III à V : morale par provision, métaphysique du ''cogito'', physique et physiologie. AT VI. * ''Méditations métaphysiques'' (1641, 1642 ; trad. fr. 1647), I à VI. AT VII (latin) ; AT IX-1 (français). * ''Principes de la philosophie'' (1644 ; trad. fr. 1647), première partie, art. 7-10, 22-24, 39, et passim ; deuxième partie, art. 37-40 (lois de la nature et principe d'inertie). AT VIII-1 ; AT IX-2. * ''Les Passions de l'âme'' (1649), première et troisième parties, en particulier art. 27, 30, 153, 161. AT XI. * ''Notae in programma'' (1648), réponse au placard de Henricus Regius. AT VIII-2. * Lettre à Mersenne du 15 avril 1630 (libre création des vérités éternelles), AT I, p. 145-146. * Lettre à Mersenne du 6 mai 1630 et du 27 mai 1630 (incompréhensibilité de Dieu et vérités éternelles), AT I, p. 149-153. * Lettres à la princesse Élisabeth de Bohême, mai-juin 1643 (notions primitives et union de l'âme et du corps) ; août-septembre 1645 (morale et béatitude), AT III et IV. * Lettre à Henry More du 5 février 1649 (statut de la pensée chez les animaux), AT V, p. 276-277. * Lettres à Denis Mesland, 2 mai 1644 et 9 février 1645 (libre arbitre, vérités éternelles, eucharistie), AT IV. {{DEFAULTSORT:Descartes}} [[Catégorie:Philosophe]] g7e6hrldocjpxy7jd89g5vxh4u8v0dl 768591 768590 2026-06-25T10:31:48Z PandaMystique 119061 768591 wikitext text/x-wiki {{DicoPhilo|René Descartes|lecture=oui}} La pensée de René Descartes constitue un tournant majeur dans l'histoire de la philosophie occidentale. Son ambition est de refonder l'édifice entier du savoir sur des bases inébranlables, en soumettant à l'examen de la raison toutes les autorités héritées et en cherchant, dans les opérations mêmes de l'esprit, les conditions de la [[Dictionnaire de philosophie/Certitude|certitude]]. == Vie et œuvres == [[Fichier:Frans Hals - Portret van René Descartes.jpg|vignette|upright=0.9|René Descartes, d'après Frans Hals (vers 1649).]] René Descartes naît le 31 mars 1596 à La Haye, en Touraine, dans une famille de petite noblesse. Envoyé vers 1606 au collège jésuite de La Flèche, alors qu'il a une dizaine d'années, il y reçoit, jusqu'en 1614 ou 1615, un enseignement encyclopédique qui le confronte à la philosophie scolastique, aux mathématiques, à la physique et aux lettres. Il obtient ensuite, en novembre 1616, une licence en droit à l'université de Poitiers. En 1618, il gagne les Provinces-Unies et s'engage dans l'armée de Maurice de Nassau, prince d'Orange. C'est là, à Breda, qu'il fait au mois de novembre la rencontre d'Isaac Beeckman, un savant néerlandais de sept ans son aîné : cet épisode compte parmi les plus importants de sa formation. Beeckman ranime son intérêt pour les questions scientifiques et lui transmet le programme d'une « physico-mathématique », c'est-à-dire l'application exacte des mathématiques aux problèmes de la physique, qui orientera durablement ses recherches ; Descartes lui dédie son ''Compendium musicae'' à la fin de l'année. Il passe ensuite, l'année suivante, au service du duc Maximilien de Bavière, voyageant à travers l'Europe. Dans la nuit du 10 au 11 novembre 1619, alors qu'il hiverne dans le sud de l'Allemagne, il rapporte avoir eu trois songes qui lui révèlent sa vocation : fonder une science universelle sur des bases entièrement nouvelles<ref>Ces songes ne nous sont pas connus directement par Descartes lui-même, qui les évoque seulement de manière allusive dans le ''Discours de la méthode'' (II, AT VI, 11), mais par le récit qu'en a transcrit son biographe Adrien Baillet, ''La Vie de Monsieur Des-Cartes'', Paris, 1691, à partir d'un cahier perdu intitulé ''Olympica''.</ref>. Installé en Hollande à partir de 1628 pour y jouir de la [[Manuel de terminale de philosophie/Liberté|liberté]] intellectuelle, Descartes consacre les deux décennies suivantes à l'élaboration de son système philosophique. Il rédige d'abord, vraisemblablement entre 1626 et 1628, les ''Regulae ad directionem ingenii'' ([[s:Règles pour la direction de l’esprit|Règles pour la direction de l'esprit]]), qu'il laisse inachevées et qui ne paraîtront qu'après sa mort. Il rédige ensuite ''[[s:Le Monde|Le Monde ou Traité de la lumière]]'', qu'il renonce à publier après la condamnation de Galilée en 1633. En 1637 paraît le ''[[s:Discours de la méthode|Discours de la méthode]]'', suivi de trois essais scientifiques (''[[s:La Dioptrique|La Dioptrique]]'', ''[[s:Les Météores|Les Météores]]'', ''[[s:La Géométrie|La Géométrie]]''). Les ''[[s:Méditations métaphysiques|Meditationes de prima philosophia]]'' paraissent en 1641 à Paris, accompagnées de six séries d'objections et de réponses ; une septième série sera ajoutée à la seconde édition de 1642, publiée à Amsterdam. Le titre de la première édition annonçait que l'ouvrage démontrait « l'existence de Dieu et l'immortalité de l'âme » ; celui de la seconde l'a remplacé par « l'existence de Dieu et la distinction réelle de l'âme et du corps de l'homme ». Les ''Principia philosophiae'' de 1644 offrent un exposé systématique de la philosophie cartésienne ; des six parties d'abord projetées, dont les deux dernières devaient traiter des vivants et de l'homme, Descartes n'en acheva que quatre, qui vont des principes de la connaissance à la cosmologie et à la physique du monde visible. Enfin, ''[[s:Les Passions de l’âme|Les Passions de l'âme]]'' paraissent en 1649, fruit d'une longue réflexion alimentée par la correspondance avec la princesse Élisabeth de Bohême. Ayant rejoint la cour de la reine Christine de Suède à Stockholm, Descartes y meurt le 11 février 1650. Dans la célèbre lettre-préface à la traduction française des ''[[s:Les Principes de la philosophie|Principes]]'' (1647), il compare la philosophie à un arbre dont les racines sont la [[Dictionnaire de philosophie/Métaphysique|métaphysique]], le tronc la physique et les branches les sciences particulières : la médecine, la mécanique et la [[Dictionnaire de philosophie/Morale|morale]]. Cette image exprime la conviction cartésienne que la métaphysique fonde la physique, laquelle rend à son tour possibles les sciences appliquées. Comprendre Descartes exige donc de parcourir l'ensemble de cet arbre, depuis ses racines métaphysiques jusqu'à ses prolongements pratiques. == La méthode == [[Fichier:Descartes Discours de la Methode.jpg|vignette|upright=0.7|Page de titre du ''Discours de la méthode'', Leyde, 1637.]] Le projet cartésien s'enracine dans une vive insatisfaction à l'égard du savoir hérité. Dès le ''Discours de la méthode'', Descartes fait le récit de sa formation intellectuelle pour en exposer les limites : hormis les mathématiques, aucune discipline ne lui a fourni de certitudes. La philosophie des écoles, malgré des siècles de culture, n'a produit que des controverses interminables. Le constat est sans appel : il faut reprendre les choses par le commencement et se doter d'une méthode rigoureuse capable de conduire l'esprit à la vérité. Les ''Regulae ad directionem ingenii'' constituent la première formulation de cette méthode. Descartes y identifie deux opérations premières de l'esprit : l'intuition et la [[Dictionnaire de philosophie/Déduction|déduction]]. L'intuition est la saisie immédiate, par un esprit attentif, d'une proposition si simple et si claire qu'aucun doute ne peut s'y glisser. La déduction est le mouvement par lequel l'esprit progresse d'une intuition à une autre, chaque étape étant elle-même évidente. Toute connaissance certaine se ramène en définitive à une chaîne d'intuitions, et la méthode consiste à organiser le travail de la pensée de manière à ne jamais rompre cette chaîne. Le ''Discours'' résume cette méthode en quatre préceptes. Le premier, dit d'évidence, commande de ne recevoir aucune chose pour vraie qu'on ne la connaisse évidemment comme telle, c'est-à-dire de n'admettre que ce qui se présente si clairement et si distinctement à l'esprit qu'il est impossible d'en douter. Le deuxième, dit d'analyse, prescrit de diviser chaque difficulté en autant de parcelles qu'il se peut, afin de la résoudre par parties. Le troisième, dit de synthèse, recommande de conduire par ordre ses pensées, en commençant par les objets les plus simples pour s'élever graduellement jusqu'aux plus composés. Le quatrième, dit de dénombrement, exige des revues si complètes qu'on soit assuré de ne rien omettre<ref>Les quatre préceptes de la méthode sont énoncés dans la deuxième partie du ''Discours de la méthode'', AT VI.</ref>. Ces quatre règles ne sont pas de simples recommandations pédagogiques : elles décrivent le fonctionnement naturel de l'intellect lorsqu'il est bien conduit, et les mathématiques en constituent le modèle par excellence. L'originalité de la méthode cartésienne réside dans sa portée universelle. Descartes ne l'envisage pas comme un instrument propre à telle ou telle discipline, mais comme le mode d'exercice de la raison elle-même. L'idée d'une ''mathesis universalis'', science générale de l'ordre et de la mesure, traduit cette ambition : toute question susceptible d'être formulée en termes de rapports et de proportions peut être résolue selon les mêmes procédures. ''La Géométrie'' de 1637, qui pose les fondements de la géométrie analytique en traduisant les figures spatiales en équations algébriques, en fournit la démonstration la plus éclatante. Il convient toutefois d'éviter de réduire à un dispositif unique les trois principaux textes méthodologiques de Descartes. Les ''Regulae'' développent une théorie de la connaissance fondée sur les natures simples et la ''mathesis universalis'', dans un horizon encore largement scientifique. Le ''Discours de la méthode'' offre, sur le mode de l'autobiographie intellectuelle, un exposé condensé et populaire destiné à un large public cultivé. Les ''Méditations'' déploient enfin une démarche métaphysique qui n'est plus strictement « méthodique » au sens des ''Regulae'', mais qui suit ce que Descartes lui-même appelle l'« ordre des raisons ». L'unité de la pensée cartésienne se manifeste dans la continuité d'un projet, non dans une stricte identité de procédure entre ces trois œuvres. Descartes reconnaîtra d'ailleurs que la méthode, si puissante soit-elle dans le domaine des sciences, ne suffit pas à elle seule à garantir la vérité de nos connaissances sur le monde : cette garantie exige un fondement d'un autre ordre, celui de la métaphysique. == La physique et la science de la nature == [[Fichier:René Descartes 1644 Principia philosophiae.jpg|vignette|upright=0.7|Page de titre des ''Principia philosophiae'', Amsterdam, 1644.]] La physique cartésienne se construit en rupture déclarée avec la physique d'inspiration aristotélicienne telle qu'elle était enseignée dans les universités depuis le Moyen Âge. Descartes expose sa conception de la nature dans ''Le Monde'' (composé vers 1633, paru en 1664), dans la cinquième partie du ''Discours'' et dans les ''Principes de la philosophie''. Le point de départ est une critique serrée de la connaissance sensible. Contre la conviction ordinaire selon laquelle les qualités perçues (couleurs, sons, odeurs, saveurs, chaleur, froid) existent réellement dans les objets extérieurs, Descartes soutient que ces qualités ne sont que des modifications de notre esprit, produites par le mouvement de la matière sur nos organes sensoriels. De même que les mots du langage n'ont aucune ressemblance avec les choses qu'ils signifient, les sensations n'ont aucune ressemblance avec les propriétés réelles des corps qui les causent. Si les sens ne nous livrent pas l'essence des choses, seules les idées claires et distinctes de l'entendement, dont les mathématiques offrent le modèle achevé, peuvent nous faire connaître l'essence des corps. La matière, dépouillée de toutes les qualités sensibles, se réduit à l'étendue en longueur, largeur et profondeur : la substance corporelle n'est rien d'autre que l'étendue. Cette identification de la matière et de l'étendue entraîne plusieurs conséquences importantes. Elle exclut d'abord le vide : puisque la matière est l'étendue elle-même, il ne saurait y avoir d'étendue sans matière, et l'espace vide est une contradiction dans les termes. Elle implique ensuite la divisibilité indéfinie de la matière : l'étendue étant divisible à l'infini, la matière l'est aussi, et il n'existe pas d'[[Dictionnaire de philosophie/Atomisme|atomes]]. Elle réduit enfin tous les phénomènes naturels au mouvement local de parties de matière étendue : il n'y a dans la nature que des figures et des mouvements. Dans ''Le Monde'', Descartes procède par une fiction ingénieuse : il invite le lecteur à imaginer la genèse d'un monde nouveau à partir d'une matière indifférenciée. Dieu crée la matière, la divise en parties et leur communique des mouvements régis par trois lois. La première loi pose que chaque partie de matière conserve son état de repos ou de mouvement tant qu'aucune cause extérieure ne vient le modifier : Descartes propose là l'une des premières formulations générales du principe d'inertie, dont cette conservation de l'état ne constitue toutefois qu'un premier aspect. Cette formulation s'inscrit dans un mouvement de réflexion plus large amorcé par Galilée et Isaac Beeckman au début du {{s|XVII}}, et Descartes en hérite autant qu'il le prolonge ; mais il lui confère une portée systématique nouvelle en l'inscrivant dans un cadre métaphysique d'ensemble. La deuxième loi établit que, dans le choc de deux corps, une certaine quantité de mouvement se conserve. Il faut noter que la « quantité de mouvement » cartésienne, calculée comme produit de la grandeur d'un corps par sa vitesse scalaire, n'est pas encore la grandeur vectorielle (impulsion) que la mécanique classique post-newtonienne fera intervenir : c'est en partie pour cette raison que les lois cartésiennes du choc se révéleront empiriquement inexactes. La troisième loi stipule que tout mouvement tend à se poursuivre en ligne droite, le mouvement rectiligne étant le seul qui se laisse déterminer en un instant ; c'est cette précision du caractère rectiligne qui achève l'énoncé cartésien de l'inertie. À partir de ces trois lois et des seuls chocs entre les parties de matière, Descartes prétend rendre compte de la formation des astres, des planètes, de la lumière et de l'ensemble des phénomènes naturels, par le biais d'un mécanisme universel de tourbillons de matière subtile. Descartes fonde ces lois sur l'immuabilité de Dieu : Dieu conserve dans le monde la même quantité de mouvement qu'il y a placée initialement, car il serait contraire à sa perfection d'en changer. Les principes les plus généraux de la physique sont ainsi rattachés à la métaphysique. Il faut cependant se garder de présenter la physique cartésienne comme entièrement déductive : si les lois générales du mouvement sont rattachées à l'immutabilité divine, les explications particulières des phénomènes naturels reposent en fait sur la construction de modèles mécaniques, la formulation d'hypothèses et le recours méthodique à l'expérience. Descartes récuse toute explication faisant appel aux causes finales : chercher à quoi sert un phénomène naturel, c'est prétendre connaître les desseins de Dieu, prétention qu'il juge aussi vaine qu'orgueilleuse. Il refuse également les formes substantielles et les qualités occultes de la physique scolastique, ne reconnaissant d'autres principes explicatifs que la matière étendue et le mouvement. L'expérimentation, dans ce cadre, n'a pas pour fonction de découvrir les principes premiers de la physique, qui sont rattachés à la métaphysique, mais de choisir entre plusieurs hypothèses mécaniques également compatibles avec ces principes. La physique cartésienne s'étend au domaine du vivant par une physiologie mécaniste. Dans la cinquième partie du ''Discours'' et dans le ''[[s:L'Homme|Traité de l'homme]]'' (publié ''post mortem''), Descartes décrit le corps humain comme une machine hydraulique d'une extrême complexité. La circulation du sang, la digestion, la respiration, le sommeil, les mouvements réflexes ainsi que l'exécution corporelle des mouvements s'expliquent par le seul jeu mécanique des organes, des nerfs, du sang et des esprits animaux (particules très fines du sang acheminées au cerveau par les artères). Les mouvements proprement volontaires, en revanche, supposent l'intervention de la volonté de l'âme, qui agit sur le corps dans le cadre de l'union substantielle. Nul besoin, par ailleurs, d'invoquer une âme végétative ou une âme sensible, comme le faisait la tradition aristotélicienne : la vie organique fonctionne comme un automate. De cette analyse découle la thèse, célèbre et controversée, de l'animal-machine : les [[Dictionnaire de philosophie/Intelligence animale|animaux]], dépourvus de raison et d'un langage propre à exprimer des idées, peuvent être considérés comme des automates. Il convient toutefois de noter que Descartes lui-même, dans une lettre à Henry More du 5 février 1649<ref>Lettre à More, 5 février 1649, AT V, p. 276-277 : Descartes y précise que, s'il tient pour démontré qu'on ne peut prouver l'existence d'une pensée chez les bêtes, il ne croit pas pour autant qu'on puisse démontrer qu'elles n'en ont aucune, l'esprit humain ne pénétrant pas dans leur vie intime.</ref>, reconnaît qu'on ne peut démontrer qu'il n'y a pas de pensée chez les bêtes ; ce qu'il nie n'est pas la vie ni la réactivité organique aux stimuli, mais la pensée au sens strict, c'est-à-dire le pouvoir réflexif et linguistique. L'[[Dictionnaire de philosophie/Âme|âme]], lorsqu'elle est présente chez l'homme, n'est pas principe de vie mais principe de pensée : elle apporte la conscience, la réflexion, le langage articulé. == La métaphysique : le projet fondateur == [[Fichier:Meditationes de prima philosophia 1641.jpg|vignette|upright=0.7|Page de titre des ''Meditationes de prima philosophia'', Paris, 1641.]] Si la physique cartésienne décrit la nature du monde au moyen d'idées claires et distinctes, encore faut-il établir que ces idées sont véridiques, c'est-à-dire qu'elles décrivent effectivement la réalité telle qu'elle est. La méthode, à elle seule, ne peut fournir cette garantie ultime. C'est à la métaphysique qu'il revient de fonder la physique en démontrant deux thèses capitales : d'une part, l'existence et la véracité de Dieu ; d'autre part, la distinction réelle de l'esprit et du corps. La première thèse est nécessaire pour garantir la vérité des idées claires et distinctes ; la seconde permet de justifier la connaissance indépendante de l'expérience sensible. Descartes expose sa métaphysique principalement dans la quatrième partie du ''Discours de la méthode'', dans les ''Méditations métaphysiques'' (1641) et dans la première partie des ''Principes''. C'est toutefois dans les ''Méditations'' que l'argumentation atteint son plus haut degré de rigueur et de pénétration. L'ouvrage se présente comme un exercice de pensée que le lecteur est invité à pratiquer avec l'auteur, une expérience intérieure de la raison progressant depuis l'incertitude la plus complète jusqu'à la certitude la plus ferme. === Le doute méthodique === La première ''Méditation'' met en œuvre un [[Dictionnaire de philosophie/Doute|doute]] systématique, volontaire et hyperbolique, dont la finalité n'est pas [[Dictionnaire de philosophie/Scepticisme|sceptique]] mais fondatrice : il s'agit de suspendre l'assentiment à toutes les opinions reçues pour ne retenir que ce qui résiste à l'épreuve du doute le plus poussé. Descartes distingue plusieurs niveaux de doute, chacun plus poussé que le précédent. Le premier niveau concerne la tromperie des sens. Nous savons par expérience que les sens nous abusent parfois : une tour carrée paraît ronde de loin, un bâton plongé dans l'eau semble brisé. Puisque les sens nous ont déjà trompés, la prudence commande de ne jamais s'y fier entièrement. Toutefois, ce doute ne touche que les perceptions de choses éloignées ou très petites ; il semble que certaines données sensibles, comme le fait que je suis ici, assis devant le feu, vêtu d'une robe de chambre, ne puissent être raisonnablement contestées. Le deuxième niveau est celui de l'argument du rêve. Il m'est arrivé souvent de rêver que j'étais ici même, assis près du feu, alors que j'étais dans mon lit. Il n'existe aucun critère parfaitement certain permettant de distinguer la veille du sommeil. Dès lors, l'existence même du monde extérieur et de mon propre corps devient incertaine. Cependant, même dans le rêve, les vérités mathématiques et les natures simples (étendue, figure, nombre) semblent subsister : que je dorme ou que je veille, deux et trois font cinq et le carré n'a que quatre côtés. Le troisième niveau, le plus poussé, est celui de l'hypothèse du Dieu trompeur, transformée ensuite en fiction du malin génie. Si Dieu est tout-puissant, rien n'empêche qu'il ait pu me donner une nature telle que je me trompe même dans les vérités qui me paraissent les plus évidentes. Les vérités mathématiques elles-mêmes tombent sous le coup de ce doute. Pour donner à cette hypothèse toute sa force, Descartes la reformule sous la figure d'un malin génie, aussi puissant que rusé, qui emploierait toute son industrie à me tromper. Ce doute hyperbolique, en suspendant toute certitude, crée les conditions d'un recommencement entier de la pensée. L'hypothèse du Dieu trompeur s'inscrit dans un contexte théologique précis. La philosophie scolastique avait longuement discuté la question de savoir si la toute-puissance divine était compatible avec la vérité de la connaissance humaine, et de nombreux théologiens avaient conclu que le savoir humain ne pouvait jamais être que provisoire et conjectural face à l'infinité divine. C'est contre cette résignation épistémologique que Descartes construit sa métaphysique. === Le ''cogito'' et la nature du sujet pensant === Au cœur du doute le plus poussé surgit une certitude inébranlable. Alors même que je suppose qu'un être tout-puissant s'emploie à me tromper en toutes choses, il est impossible que je n'existe pas au moment même où je pense être trompé. Pour me tromper, il faut bien que je sois. Cette première vérité reçoit chez Descartes plusieurs formulations selon les ouvrages : « je pense, donc je suis » dans la quatrième partie du ''Discours de la méthode'', « je suis, j'existe » (''ego sum, ego existo'') dans la deuxième ''Méditation'', où la formule canonique de la conclusion est en réalité l'affirmation simple de l'existence à chaque fois que je la prononce ou la conçois, et « je pense, donc je suis » (''ego cogito, ergo sum'') dans les ''Principes de la philosophie''. C'est cette dernière formule que la tradition désignera comme « le ''cogito'' », expression elle-même postérieure à Descartes. Quel que soit le libellé, il s'agit du roc sur lequel s'édifiera tout l'édifice du savoir. Le statut logique exact de cette proposition a suscité d'amples discussions parmi les commentateurs : s'agit-il d'un syllogisme (qui supposerait une prémisse majeure « tout ce qui pense existe »), ou d'une intuition immédiate par laquelle la pensée se saisit elle-même dans l'exercice même de son doute ? Descartes lui-même, dans les ''Secondes Réponses'', insiste sur le caractère non syllogistique de cette certitude : la conclusion « j'existe » est aperçue « par une simple inspection de l'esprit ». Sa vérité est établie par le fait que toute tentative de la nier la confirme : si je pense que je n'existe pas, j'existe ; si je suis trompé, j'existe. Du ''[[Dictionnaire de philosophie/Cogito|cogito]]'', Descartes tire aussitôt un enseignement sur la nature du moi. La certitude d'exister ne dépend que de la pensée : si je cessais de penser, rien ne m'assurerait plus de mon existence. En revanche, je puis concevoir que je n'ai pas de corps tout en restant certain d'exister comme être pensant. La pensée, prise dans toute l'étendue de ses modalités (douter, concevoir, affirmer, nier, vouloir, refuser, imaginer, sentir), constitue donc l'attribut essentiel du moi. Le moi est une « chose qui pense » (''res cogitans''), une substance dont toute l'essence consiste dans la pensée. Cette détermination du moi par la seule pensée, indépendamment de toute référence au corps, marque une rupture nette avec la tradition aristotélicienne, pour laquelle la connaissance de l'esprit dérivait de sa réflexion sur les objets corporels. Descartes illustre cette thèse par la célèbre analyse du morceau de cire, dans la deuxième ''Méditation''<ref>L'analyse du morceau de cire se trouve dans la deuxième ''Méditation'', AT VII ; trad. fr. AT IX-1.</ref>. Un morceau de cire fraîchement tiré de la ruche possède une couleur, une odeur, une figure, une taille déterminées. Si on l'approche du feu, toutes ces qualités sensibles changent : la couleur disparaît, l'odeur s'évanouit, la figure se modifie. Pourtant, nous jugeons que c'est toujours le même morceau de cire. Ce jugement d'identité ne peut, selon Descartes, venir des sens, puisque toutes les qualités sensibles ont changé. Il résulte d'un acte de l'entendement, qui saisit la cire comme une substance étendue, flexible et muable, propriétés que l'imagination ne peut embrasser dans leur infinité mais que l'esprit seul conçoit. Ainsi, même la connaissance des corps suppose l'intervention de l'entendement et de ses idées, et non la seule réception passive des données sensibles. Le doute et le ''cogito'' ont donc conjointement établi deux résultats : l'existence certaine du moi pensant et la primauté de la connaissance intellectuelle sur la connaissance sensible. Reste à savoir si cette certitude du moi peut s'étendre à d'autres réalités, et en premier lieu à Dieu. === Les preuves de l'existence de Dieu === Le ''cogito'' fournit un premier fondement, mais il ne suffit pas à garantir la vérité de toutes nos idées claires et distinctes, car celles-ci restent menacées par l'hypothèse du Dieu trompeur. Pour lever cette menace, il faut démontrer que Dieu existe et qu'il n'est pas trompeur. Descartes propose trois preuves de l'existence de Dieu dans les troisième et cinquième ''Méditations''<ref>Les preuves a posteriori, par l'idée d'infini et par la cause du sujet pensant, occupent la troisième ''Méditation'' ; la preuve ontologique se déploie dans la cinquième. AT VII ; trad. fr. AT IX-1.</ref>. La première preuve, dite preuve par l'effet, part de l'idée de Dieu que l'esprit trouve en lui-même. Descartes distingue dans toute idée deux aspects : sa réalité formelle, c'est-à-dire son être en tant que mode de la pensée, et sa réalité objective, c'est-à-dire le contenu qu'elle représente. Or, la réalité objective d'une idée doit avoir une cause qui possède au moins autant de réalité formelle. L'idée de Dieu est l'idée d'une substance infinie, éternelle, immuable, toute-puissante, omnisciente et créatrice de toutes choses. La réalité objective de cette idée excède de manière incommensurable la réalité formelle du moi, substance finie et imparfaite. Le moi ne peut donc pas être la cause de l'idée de Dieu : seul un être effectivement infini et parfait peut avoir produit en nous cette idée. Donc Dieu existe. Cette preuve suppose que l'idée d'infini est positive et originaire, et non simplement dérivée de la négation du fini. Descartes s'oppose ici à la position thomiste : loin que l'idée d'infini résulte de la négation des limites du fini, c'est au contraire l'idée du fini qui suppose celle de l'infini, car je ne pourrais pas me reconnaître fini et imparfait si je n'avais en moi l'idée d'un être infini et parfait par comparaison auquel je me juge déficient. La deuxième preuve, développée également dans la troisième ''Méditation'', cherche la cause de l'existence du moi en tant qu'il possède l'idée de Dieu. Je n'ai pas pu me créer moi-même, car si j'avais eu ce pouvoir, je me serais donné toutes les perfections dont j'ai l'idée. Aucune cause moins parfaite que Dieu ne peut être la cause ultime de mon existence : la régression à l'infini étant impossible, il faut nécessairement aboutir à un être qui a en soi le principe de son existence et qui possède toutes les perfections contenues dans l'idée de Dieu. La troisième preuve, exposée dans la cinquième ''Méditation'', est la preuve ontologique. Elle procède de la seule définition de Dieu comme être souverainement parfait. Tout comme certaines propriétés appartiennent nécessairement à l'essence d'une figure géométrique (les trois angles d'un triangle valent ensemble deux droits), l'existence nécessaire appartient à l'essence d'un être souverainement parfait. Il serait donc contradictoire de concevoir un tel être auquel manquerait l'existence : sa nature même implique qu'il existe. L'existence appartient ainsi à l'essence de Dieu, et nier son existence reviendrait à se contredire. Kant qualifiera plus tard cet argument de « preuve ontologique », et lui reprochera de traiter l'existence comme un prédicat susceptible d'enrichir le concept d'une chose, alors que, selon lui, l'existence n'ajoute rien au contenu d'un concept. L'objection kantienne ne rend toutefois pas pleinement compte du raisonnement cartésien, qui n'ajoute pas l'existence à une liste de perfections : il infère sa nécessité de la considération de l'essence divine prise en elle-même. === La véracité divine et le problème du cercle === L'existence de Dieu une fois établie, Descartes en déduit que Dieu, être souverainement parfait, ne saurait être trompeur, car la tromperie suppose un défaut incompatible avec la perfection divine. Si Dieu n'est pas trompeur, alors la lumière naturelle qu'il a mise en nous, c'est-à-dire notre faculté de connaître les choses clairement et distinctement, est fiable. Tout ce que nous concevons clairement et distinctement est vrai. La menace du malin génie est définitivement écartée, et la science humaine reçoit son fondement ultime dans la véracité divine. On a objecté à Descartes, dès la parution des ''Méditations'', que son raisonnement serait circulaire : il invoque Dieu pour garantir la vérité des idées claires et distinctes, mais il démontre l'existence de Dieu au moyen d'idées claires et distinctes<ref>L'objection est formulée notamment par Antoine Arnauld dans les ''Quatrièmes Objections'' ; Descartes y répond dans les ''Secondes Réponses'' et les ''Quatrièmes Réponses''. AT VII ; trad. fr. AT IX-1.</ref>. Ce « cercle cartésien » a suscité d'innombrables commentaires, et les interprètes ne s'accordent pas sur la portée exacte de la réponse de Descartes. Celui-ci distingue entre les intuitions actuelles, dont l'esprit ne peut douter au moment même où il les perçoit, et les résultats de démonstrations passées, dont on pourrait douter si l'on n'avait pas la garantie divine. La véracité de Dieu ne serait donc pas requise pour assurer les évidences présentes, celles-ci s'imposant d'elles-mêmes, mais pour garantir que ce qui a été clairement et distinctement perçu reste vrai même lorsque l'esprit ne se le représente plus actuellement. La garantie divine porterait ainsi sur la mémoire de nos évidences et sur la fiabilité de nos raisonnements longs et complexes. == La théorie des idées == La métaphysique cartésienne repose sur une théorie des idées qui reprend certains thèmes de la tradition platonicienne, en particulier la thèse selon laquelle les connaissances les plus hautes ne proviennent pas des sens. Descartes n'adopte pas pour autant le cadre métaphysique de Platon (il ne postule ni un monde séparé des Formes, ni la réminiscence), mais il partage avec lui la conviction que l'expérience sensible ne fournit pas, à elle seule, de connaissance certaine. Descartes distingue trois sortes d'idées selon leur origine. Les idées adventices semblent provenir de l'expérience extérieure : ce sont les sensations de couleur, de son, de chaleur, etc. Les idées factices sont fabriquées par l'[[Dictionnaire de philosophie/Imagination|imagination]], comme l'idée de sirène ou de chimère. Les idées innées, enfin, se trouvent en nous indépendamment de toute expérience sensible : ce sont les idées de Dieu, de l'âme, de l'étendue, des figures géométriques, des vérités mathématiques et logiques. Dans les ''Notae in programma'' (1648), réponse au placard de Regius, Descartes précise que les idées innées ne sont pas des contenus présents à l'esprit sous une forme toute faite, mais des dispositions ou des aptitudes que l'esprit possède de produire ces idées par sa propre puissance. L'innéité ne désigne donc pas une présence actuelle de toutes les essences dans la conscience, mais la faculté naturelle de l'esprit de former ces idées sans aide extérieure. Les idées innées se distinguent des autres par plusieurs caractéristiques. Elles ne sont pas imposées par un objet extérieur, mais produites par la puissance propre de l'entendement ; leur contenu, en revanche, s'impose à l'esprit avec nécessité et ne peut être modifié par la volonté. L'esprit est passif devant ce contenu : les vérités mathématiques ne sont pas des inventions mais des découvertes, comme l'atteste le fait que les progrès mathématiques se font pas à pas, chaque nouvelle vérité s'imposant à l'esprit avec une nécessité que celui-ci ne contrôle pas. Cette nécessité interne du contenu indique, selon Descartes, qu'il y a là plus qu'une simple production subjective : les idées innées correspondent à ce qu'il appelle, dans la cinquième ''Méditation'', des « vraies et immuables natures », objets stables auxquels la pensée se rapporte. Ces natures ne forment cependant pas, chez Descartes, un domaine d'essences entièrement indépendantes au sens où l'auraient compris certains platoniciens : ainsi que le précise la doctrine de la libre création des vérités éternelles, elles dépendent entièrement de la volonté divine qui les a librement instituées. L'innéisme cartésien doit donc se comprendre dans cet équilibre subtil : objectivité réelle des idées et des essences qu'elles représentent, garantie par la véracité divine ; absence d'autonomie ontologique de ces essences à l'égard de leur cause créatrice. La théorie des idées innées, associée à la véracité divine, constitue le fondement de l'anti-[[Dictionnaire de philosophie/Empirisme|empirisme]] cartésien. La connaissance authentique du monde ne passe pas par les sens, mais par l'entendement pur et ses idées innées. Les idées adventices, quant à elles, ne nous informent pas sur la nature réelle des choses, mais elles remplissent une autre fonction : elles incitent irrésistiblement l'esprit à croire à l'existence d'un monde extérieur. Si les corps n'existaient pas, Dieu serait trompeur, puisqu'il nous a naturellement portés à croire à leur existence sans nous donner aucun moyen de corriger cette croyance. La véracité divine garantit donc l'existence du monde extérieur, mais c'est par les idées innées que nous en connaissons la nature. Il faut souligner le caractère paradoxal de cet ordre : Descartes connaît la nature du monde (l'étendue géométrique) avant d'en avoir établi l'existence réelle. == La théorie de la libre création des vérités éternelles == Une dimension essentielle de la métaphysique cartésienne tient à la doctrine de la libre création des vérités éternelles. Formulée pour la première fois dans la lettre à Mersenne du 15 avril 1630<ref>Lettre à Mersenne du 15 avril 1630, AT I, p. 145 : « Les vérités mathématiques, lesquelles vous nommez éternelles, ont été établies de Dieu et en dépendent entièrement, aussi bien que tout le reste des créatures. » Voir aussi les lettres du 6 mai et du 27 mai 1630, AT I, p. 149-153.</ref>, cette doctrine affirme que les vérités éternelles (y compris les vérités mathématiques et les lois logiques) ont été librement instituées par Dieu. Dieu n'est pas soumis aux vérités éternelles comme à des contraintes extérieures ; c'est lui qui les a établies par un acte de volonté libre. Que deux et trois fassent cinq, que le tout soit plus grand que la partie, que les contradictoires ne puissent être vraies ensemble : tout cela ne résulte pas d'une nécessité que Dieu subirait, mais d'un décret divin. Dieu aurait pu en décider autrement ; mais notre esprit fini est incapable de se représenter positivement en quoi consisterait un tel ordre. Reconnaître que les vérités nécessaires dépendent de la volonté divine n'est donc pas se donner le pouvoir de concevoir une autre logique : c'est seulement admettre que leur nécessité même est instituée, et qu'elle excède notre compréhension. Cette thèse, d'une grande audace, porte à son comble l'affirmation de la toute-puissance divine. Sans être entièrement nouvelle (la tradition scotiste avait, à sa manière, déjà étendu la liberté divine très loin), elle revêt chez Descartes une portée nouvelle et une intensité particulière. Sa portée exacte et son articulation avec le reste de la métaphysique cartésienne font l'objet de discussions persistantes parmi les commentateurs. Bien qu'absente des ''Méditations'' proprement dites, la doctrine n'est pas confinée à la correspondance : Descartes la formule dans les ''Cinquièmes'' et ''Sixièmes Réponses aux Objections'', ainsi que dans certaines parties des ''Principes'' (I, 22-24)<ref>''Principes de la philosophie'', I, art. 22-24, AT VIII-1, p. 13-14 ; AT IX-2, p. 36-37. Sur cette doctrine, voir Jean-Luc Marion, ''Sur la théologie blanche de Descartes'', Paris, PUF, 1981.</ref>. Elle n'est cependant pas explicitement mobilisée dans la construction des preuves métaphysiques de l'existence de Dieu. == L'erreur et le libre arbitre == Si Dieu est vérace et a doté l'homme d'une faculté de connaître fiable, comment se fait-il que celui-ci se trompe ? La quatrième ''Méditation'' est consacrée à cette question. L'[[Dictionnaire de philosophie/Erreur|erreur]] résulte du concours de deux facultés : l'entendement, qui est fini, et la volonté, qui est infinie. L'entendement ne conçoit qu'un nombre limité d'idées claires et distinctes. La volonté, en revanche, s'étend à tout : elle peut affirmer ou nier au-delà de ce que l'entendement lui présente clairement. L'erreur survient lorsque la volonté se prononce sur des matières que l'entendement ne conçoit pas clairement et distinctement. Elle n'est donc pas imputable à Dieu, mais au mauvais usage que l'homme fait de sa liberté. Dieu a créé l'homme avec un entendement fini et une volonté infinie, et cette disproportion n'est pas un défaut mais le signe de la grandeur de l'homme : c'est par la volonté, qui est selon Descartes « la plus grande perfection de l'homme », que nous sommes faits à l'image de Dieu. La liberté humaine occupe ainsi une place centrale dans la métaphysique cartésienne. Descartes la conçoit comme le pouvoir de choisir entre des contraires, mais aussi et surtout comme la capacité de se déterminer par la seule lumière de l'entendement. La plus haute forme de liberté n'est pas l'indifférence, état dans lequel la volonté ne penche vers aucun côté, mais la détermination éclairée par laquelle l'esprit adhère au vrai et au bien qu'il connaît clairement. Plus l'entendement voit clairement, plus la volonté s'y porte infailliblement, et plus la liberté est grande. Ce double visage de la liberté cartésienne, qui oppose liberté d'indifférence et liberté éclairée, fera l'objet de discussions importantes dans la postérité du cartésianisme. == Le dualisme : distinction et union de l'âme et du corps == La sixième ''Méditation'' établit la distinction réelle de l'âme et du corps. Puisque l'esprit peut être conçu clairement et distinctement sans le corps, et le corps sans l'esprit, et puisque Dieu peut réaliser tout ce qui est clairement et distinctement conçu, l'âme et le corps sont deux substances réellement distinctes, capables d'exister séparément. L'âme est une substance pensante (''res cogitans''), dont l'essence est la pensée ; le corps est une substance étendue (''res extensa''), dont l'essence est l'étendue en trois dimensions. Ce [[Dictionnaire de philosophie/Dualisme|dualisme]] des substances est l'une des thèses fondatrices de la philosophie cartésienne : il rend intelligible l'idée que l'esprit possède des ressources propres qui ne dérivent pas de l'expérience sensible (l'âme, étant indépendante du corps, peut posséder des idées qui ne proviennent pas de l'expérience corporelle), justifie la physique mécaniste (les corps, n'étant qu'étendue, n'ont besoin pour être expliqués que de figures et de mouvements) et prépare la thèse de l'immortalité de l'âme. Sur ce dernier point, il faut toutefois souligner que les ''Méditations'' n'apportent pas une démonstration directe de l'immortalité personnelle ; elles établissent la distinction réelle, dont on peut inférer que l'âme n'est pas naturellement détruite par les mêmes causes que le corps. Descartes lui-même, en réponse à des objections, présente cette distinction comme une condition de l'immortalité plutôt que comme sa preuve achevée, dont l'établissement définitif relève selon lui de la théologie. Cependant, le dualisme cartésien ne s'en tient pas à la seule distinction. L'expérience quotidienne atteste que l'âme et le corps, bien que réellement distincts, sont étroitement unis. La douleur, le plaisir, la faim, la soif ne sont pas de pures pensées détachées du corps, mais des sentiments confus résultant de l'union de l'âme et du corps. L'âme n'est pas dans le corps comme un pilote en son navire, qui percevrait les avaries du vaisseau de l'extérieur : elle lui est intimement mêlée, de sorte que les affections du corps se traduisent immédiatement en modifications de l'âme. Dans les ''Quatrièmes Réponses aux Objections'', Descartes parle d'une union qui n'est « pas accidentelle, mais substantielle ». L'expression, empruntée au vocabulaire scolastique, est cependant employée par Descartes dans un sens largement transformé : il ne s'agit plus de l'union d'une forme et d'une matière au sens aristotélicien, mais d'un fait d'expérience irréductible à la seule distinction rationnelle des substances. La princesse Élisabeth lui adressera d'ailleurs, dès 1643, des objections aiguës sur la manière dont deux substances aussi hétérogènes peuvent agir l'une sur l'autre, objections auxquelles Descartes peinera à répondre de manière entièrement satisfaisante. Descartes cherche ainsi à se frayer une voie entre les modèles platonicien et aristotélicien. Platon avait raison de soutenir que l'âme et le corps sont deux réalités distinctes, mais il n'a pas suffisamment rendu compte de leur union intime. [[Dictionnaire de philosophie/Aristote|Aristote]] avait raison de concevoir leur union comme celle d'une forme et d'une matière constituant un seul tout, mais il n'avait pas, aux yeux de Descartes, suffisamment reconnu leur distinction substantielle. L'originalité de la position cartésienne est de maintenir ensemble ces deux thèses apparemment contradictoires. La distinction réelle est établie par la raison ; l'union, quant à elle, est saisie par une notion primitive ''sui generis'' et par l'expérience intérieure que chacun en fait, comme l'explique Descartes dans la lettre à Élisabeth du 21 mai 1643<ref>Lettre à Élisabeth du 21 mai 1643, AT III, p. 663-668. Descartes y distingue trois sortes de notions primitives : celles qui se rapportent à l'âme seule, celles qui se rapportent au corps seul, et celle de l'union, par laquelle nous concevons que l'âme meut le corps et que le corps cause des sentiments dans l'âme.</ref>. Cette union a une finalité pratique : les sensations, bien qu'elles déforment la réalité du point de vue de la connaissance spéculative, informent correctement sur ce qui est utile ou nuisible à la conservation du composé âme-corps. L'homme se trompe non pas en ayant des sensations, mais lorsqu'il utilise ces informations pratiques à des fins de connaissance théorique. [[Fichier:Descartes mind and body.gif|vignette|upright=0.9|Figure du ''Traité de l'homme'' : les mouvements transmis par les nerfs convergent vers la glande pinéale, siège supposé de l'interaction entre l'âme et le corps.]] Sur la question du lieu de l'interaction entre l'âme et le corps, Descartes identifie la glande pinéale (ou ''conarium'') comme le siège principal de l'âme, non parce que l'âme y serait enfermée (l'âme étant immatérielle ne saurait être localisée comme un corps), mais parce que c'est la seule partie du cerveau qui ne soit pas double et qui puisse servir de point de convergence entre les mouvements des esprits animaux et les actes de la pensée<ref>Sur la glande pinéale comme siège des fonctions de l'âme, voir ''Les Passions de l'âme'', I, art. 31-32, AT XI, ainsi que le ''Traité de l'homme'', AT XI.</ref>. Cette hypothèse physiologique, vivement critiquée dès le vivant de Descartes, a le mérite de poser explicitement le problème de l'interaction entre deux substances hétérogènes, problème qui occupera toute la philosophie post-cartésienne. La physique cartésienne accroît encore cette difficulté : puisque la quantité de mouvement, grandeur scalaire, se conserve, on voit mal comment l'âme pourrait l'augmenter sans contrevenir à ce principe. Descartes lui-même n'affirme pas que l'âme se bornerait à modifier la direction des esprits animaux sans rien ajouter à leur mouvement ; ce sont des cartésiens postérieurs qui avancèrent cette solution, à laquelle Leibniz objecta qu'un changement de direction suppose lui aussi une modification de la quantité de mouvement et demeure donc incompatible avec sa conservation. La question du dualisme ne concerne pas seulement la métaphysique : elle engage directement la morale. Car c'est de l'union de l'âme et du corps que naissent les passions, et c'est en apprenant à régler cette union que l'homme peut espérer bien conduire sa vie. == La morale == La morale, dans l'image de l'arbre des sciences, constitue l'une des branches les plus élevées. Descartes n'a pas eu le temps de rédiger un traité de morale systématique, mais sa pensée éthique se déploie dans le ''Discours de la méthode'', dans la correspondance avec la princesse Élisabeth de Bohême et avec Pierre Chanut, et dans ''Les Passions de l'âme''. Dans la troisième partie du ''Discours'', Descartes présente une « morale par provision », destinée à guider la conduite pendant que l'esprit est occupé à reconstruire l'édifice du savoir. Cette morale provisoire comporte trois maximes principales. La première prescrit d'obéir aux lois et aux coutumes de son pays, de conserver la religion dans laquelle on a été élevé et de suivre les opinions les plus modérées. La deuxième recommande la fermeté et la résolution dans les actions : une fois qu'on a adopté une opinion, il faut la suivre avec constance, même si elle est incertaine, plutôt que de demeurer irrésolu. La troisième maxime, d'inspiration stoïcienne, conseille de changer ses [[Dictionnaire de philosophie/Désir|désirs]] plutôt que l'ordre du monde, c'est-à-dire de limiter ses aspirations à ce qui est vraiment en notre pouvoir (nos pensées seules le sont), et de considérer tout le reste comme indépendant de notre volonté. La correspondance avec Élisabeth, entre 1643 et 1649, permet à Descartes de préciser et d'approfondir sa pensée morale au-delà de la morale provisoire. Le souverain bien de l'homme est la béatitude, qui ne se confond pas avec le [[Dictionnaire de philosophie/Bonheur|bonheur]] dépendant des circonstances extérieures, mais consiste en un contentement intérieur de l'esprit résultant du bon usage de la volonté. Trois conditions sont requises pour y parvenir : connaître ce qui est réellement bien, c'est-à-dire ce que la raison nous enseigne ; avoir la ferme résolution de faire tout ce que la raison nous conseille ; limiter nos désirs à ce qui dépend de nous. Descartes ajoute à ces principes la méditation de certaines vérités essentielles : l'existence d'un Dieu dont tout dépend, la distinction de l'âme et du corps qui nous assure de la possibilité de survivre à la mort, l'immensité de l'univers qui relativise nos attachements terrestres, et le sentiment d'appartenir à une communauté humaine dont le bien doit l'emporter sur notre bien particulier. == Les passions de l'âme == [[Fichier:Charles Le Brun - Expressions of the Passions of the Soul - WGA12555.jpg|vignette|upright=0.8|Planche d'expressions des passions par Charles Le Brun (vers 1670), dans le sillage de Descartes.]] Le dernier ouvrage publié du vivant de Descartes, ''Les Passions de l'âme'' (1649), est issu d'une longue réflexion alimentée par la correspondance avec la princesse Élisabeth, à qui Descartes avait envoyé en 1646 un premier traité qui constitua l'embryon de l'ouvrage publié. Il s'agit à la fois d'un traité de physiologie et d'un traité de morale. Descartes y entreprend d'étudier les passions « en physicien », c'est-à-dire de les expliquer par leurs causes naturelles et mécaniques, sans visée rhétorique ni moraliste. Les passions sont définies comme des perceptions, sentiments ou émotions de l'âme, causées et entretenues par certains mouvements des esprits animaux. Elles sont causées par le corps, mais ressenties par l'âme, et constituent l'un des effets les plus manifestes de l'union de l'âme et du corps. Descartes identifie six passions primitives, dont toutes les autres sont des espèces ou des combinaisons : l'admiration, l'[[Dictionnaire de philosophie/Amour|amour]], la haine, le désir, la joie et la tristesse. L'admiration occupe une place à part, car elle est « la première de toutes » et nous dispose à apprendre et à connaître les choses nouvelles. Les autres passions se rapportent au bien et au mal, présent ou futur. Toutes les passions, considérées en elles-mêmes, sont bonnes et utiles : elles portent l'âme à vouloir les choses que la nature lui enseigne être utiles et à persévérer dans cette volonté. Une vie sans passions serait une vie appauvrie, dépourvue de saveur et de plaisir. Descartes se distingue ici nettement du stoïcisme, qui prônait l'éradication des passions. Néanmoins, les passions ont besoin d'être réglées, car leurs excès peuvent conduire à des actions nuisibles. Descartes estime cependant que la raison ne peut pas combattre directement les passions par la seule force de ses arguments : les passions procèdent de mouvements physiologiques que seuls des mouvements contraires peuvent contrebalancer. La stratégie consiste donc à susciter des passions contraires à celles que l'on veut maîtriser, en portant l'attention sur les pensées qui leur sont ordinairement associées. L'esprit peut aussi tirer parti du caractère arbitraire de la relation entre mouvements corporels et pensées de l'âme : par l'habitude et l'exercice, il est possible de modifier les associations naturelles et d'obtenir, par exemple, que la vue d'un danger suscite la hardiesse plutôt que la peur. Ce travail sur les passions, que Descartes compare au dressage d'un animal, fait de l'homme l'artisan de sa propre nature. La vertu cardinale de la morale cartésienne est la générosité, que les ''Passions de l'âme'' présentent comme « la clé de toutes les autres vertus » (art. 161)<ref>Sur la générosité, sa définition et ses propriétés, voir ''Les Passions de l'âme'', III, art. 153, 156 et 161, AT XI.</ref>. Elle se définit par deux traits indissociables : la reconnaissance que rien ne nous appartient en propre que la libre disposition de nos volontés, et la ferme et constante résolution de bien user de ce libre arbitre. L'homme généreux ne s'estime que pour son bon usage de la liberté, ne méprise personne, n'envie rien et affronte les événements avec résolution et tranquillité. La générosité est à la fois une passion et une vertu : c'est le sentiment de notre propre liberté érigé en principe de conduite. Apparue tardivement dans la pensée cartésienne, elle réalise une synthèse originale entre plusieurs traditions éthiques : magnanimité aristotélicienne, fermeté stoïcienne, humilité chrétienne et culture épicurienne d'un plaisir épuré. Elle conduit à l'estime de soi fondée sur le bon usage du libre arbitre, et à une bienveillance universelle, puisque celui qui reconnaît que la vraie grandeur réside dans le bon usage de la volonté ne peut manquer de la reconnaître aussi chez autrui. == L'héritage cartésien == L'influence de Descartes sur la philosophie moderne est vaste et multiforme. En fondant la philosophie sur le [[Dictionnaire de philosophie/Sujet|sujet]] pensant, il devient l'un des points de départ majeurs de ce que l'on a pu appeler la « philosophie de la [[Dictionnaire de philosophie/Conscience|conscience]] », qui marque la pensée occidentale jusqu'à la fin du {{s|XIX}}. Une telle primauté du rapport de soi à soi doit toutefois être nuancée : elle connaît des antécédents augustiniens et scolastiques, et la modernité philosophique ne se réduit pas à cette seule lignée. La réception du cartésianisme commence dès le vivant de Descartes, dans des conditions souvent conflictuelles. Aux Provinces-Unies, le théologien calviniste Gisbert Voet (Voetius) attaque dès 1640 la philosophie cartésienne, soupçonnée d'athéisme, et fait condamner son enseignement à l'université d'Utrecht. La querelle se prolongera durant des années et inspirera à Descartes son ''Épître à Voetius'' (1643)<ref>Sur ces querelles, voir Theo Verbeek, ''Descartes and the Dutch : Early Reactions to Cartesian Philosophy, 1637-1650'', Carbondale, Southern Illinois University Press, 1992.</ref>. En France, l'œuvre cartésienne suscite également des controverses dans les universités, qui restent largement aristotéliciennes, et l'enseignement du cartésianisme y sera plusieurs fois entravé : en 1671 puis en 1691, des interdictions seront prononcées contre la diffusion des thèses cartésiennes dans les collèges. Les œuvres de Descartes seront en partie mises à l'Index par Rome en 1663<ref>Sur ces épisodes, voir Tad M. Schmaltz, ''Radical Cartesianism : The French Reception of Descartes'', Cambridge, Cambridge University Press, 2002. Sur la question particulière de l'eucharistie, voir Jean-Robert Armogathe, ''Theologia cartesiana : l'explication physique de l'Eucharistie chez Descartes et dom Desgabets'', La Haye, Martinus Nijhoff, 1977.</ref>. Un point particulièrement sensible concerne l'eucharistie : la réduction cartésienne de la substance corporelle à l'étendue rend difficile l'explication scolastique de la transsubstantiation, qui supposait la persistance d'accidents sans substance. Descartes, conscient du problème, propose dans les ''Quatrièmes Réponses aux Objections'' et dans la correspondance avec Mesland<ref>Lettre à Mesland du 9 février 1645, AT IV, p. 161-170.</ref> une réinterprétation du dogme à partir de sa propre métaphysique ; ces explications ne convaincront pas tous les théologiens et alimenteront la suspicion d'hétérodoxie. À Port-Royal, en revanche, le cartésianisme rencontre un accueil largement favorable. Antoine Arnauld, auteur des ''Quatrièmes Objections'' aux ''Méditations'', est l'un des premiers grands défenseurs de la philosophie cartésienne ; il en utilise les éléments dans ''La Logique ou l'Art de penser'' (1662), composée avec Pierre Nicole. Blaise Pascal, plus réservé, prend cependant ses distances à l'égard d'une métaphysique qu'il juge trop ambitieuse et trop indifférente aux questions existentielles : son célèbre jugement, « je ne puis pardonner à Descartes »<ref>Blaise Pascal, ''[[s:Pensées (Pascal)|Pensées]]'', éd. Lafuma, fragment 1001 (éd. Brunschvicg, fragment 77) : « Je ne puis pardonner à Descartes : il aurait bien voulu, dans toute sa philosophie, se pouvoir passer de Dieu ; mais il n'a pu s'empêcher de lui faire donner une chiquenaude, pour mettre le monde en mouvement ; après cela, il n'a plus que faire de Dieu. »</ref>, exprime une réticence au projet cartésien d'autonomie de la raison. Sur le plan strictement philosophique, le dualisme cartésien soulève des difficultés que reprendront les principaux successeurs. La question de l'interaction entre deux substances hétérogènes occupera Malebranche, Spinoza et Leibniz. Malebranche, refusant toute action du corps sur l'âme, développe l'occasionalisme : Dieu seul est cause efficiente, et les événements corporels ne sont que l'occasion des modifications de l'âme. Spinoza, récusant le dualisme lui-même, identifie la pensée et l'étendue comme deux attributs d'une substance unique, Dieu ou la Nature. Leibniz propose l'harmonie préétablie, selon laquelle l'âme et le corps suivent chacun leurs propres lois sans interaction réelle, leurs séries d'événements étant coordonnées de toute éternité par Dieu. Sur le plan scientifique, la physique cartésienne joue un rôle de premier plan dans l'établissement du mécanisme moderne. La théorie des tourbillons offre un modèle cosmologique cohérent qui sera enseigné dans une grande partie de l'Europe pendant près d'un siècle. Mais la publication des ''Principia mathematica'' de Newton en 1687 ouvre une longue confrontation entre cartésiens et newtoniens : la physique des tourbillons, qui prétendait expliquer mécaniquement le mouvement des planètes par les chocs de la matière subtile, se voit progressivement supplantée par la mécanique newtonienne, qui introduit une attraction universelle agissant à distance. Au {{s|XVIII}}, le modèle cartésien est encore défendu en France, notamment par Bernard de Fontenelle, ainsi que dans certains milieux savants des Provinces-Unies ; mais l'autorité de Newton finit par s'imposer dans la communauté savante européenne au milieu du siècle. La géométrie analytique, en revanche, demeure l'une des contributions majeures de Descartes à l'histoire des mathématiques. La méthode du doute, la primauté accordée à la certitude du ''cogito'', la théorie des idées innées et ce que la tradition postérieure interprétera comme une distinction du sujet et de l'objet, formule rétrospective qui n'appartient pas au vocabulaire de Descartes lui-même, constituent des points de référence obligés de toute la philosophie ultérieure. Locke et Hume contestent l'innéisme en faisant fond sur l'expérience, tandis que Kant entreprend de refonder la métaphysique à partir d'une critique de la raison pure dans laquelle il reproche à Descartes son traitement de l'existence comme prédicat. Au {{s|XX}}, Husserl revendique explicitement l'héritage cartésien dans ses ''Méditations cartésiennes'' (1931), tout en transformant le ''cogito'' en sujet transcendantal de la phénoménologie. Sartre, Merleau-Ponty et plus largement la phénoménologie française entretiendront un rapport complexe à Descartes, fait à la fois de reprise et de critique. Les sciences cognitives et les philosophies analytiques de l'esprit héritent quant à elles, sur le mode souvent polémique, du problème des rapports entre l'esprit et le corps que Descartes a légué à la modernité. == Notes et références == <references /> == Bibliographie == === Éditions des œuvres de Descartes === * Charles Adam et Paul Tannery (éd.), ''Œuvres de Descartes'', 11 vol., Paris, Léopold Cerf, 1897-1913 ; nouvelle présentation augmentée, 11 vol., Paris, Vrin-CNRS, 1964-1974 (référence usuelle, citée AT suivi du tome et de la page). * Ferdinand Alquié (éd.), ''Œuvres philosophiques de Descartes'', 3 vol., Paris, Garnier, 1963-1973. * René Descartes, ''Œuvres complètes'', Paris, Gallimard, « Bibliothèque de la Pléiade », vol. I, 2009 (édition dirigée par Jean-Marie et Michelle Beyssade). === Études biographiques et générales === * Stephen Gaukroger, ''Descartes : une biographie intellectuelle'', traduit de l'anglais, Paris, PUF, 1997 (édition originale Oxford, 1995). * Geneviève Rodis-Lewis, ''Descartes. Biographie'', Paris, Calmann-Lévy, 1995. * Geneviève Rodis-Lewis, ''L'Œuvre de Descartes'', 2 vol., Paris, Vrin, 1971. * John Cottingham (dir.), ''The Cambridge Companion to Descartes'', Cambridge, Cambridge University Press, 1992. === Études sur la métaphysique et la théorie de la connaissance === * Ferdinand Alquié, ''La Découverte métaphysique de l'homme chez Descartes'', Paris, PUF, 1950. * Martial Gueroult, ''Descartes selon l'ordre des raisons'', 2 vol. (''L'âme et Dieu'' ; ''L'âme et le corps''), Paris, Aubier, 1953. * Jean-Marie Beyssade, ''La Philosophie première de Descartes'', Paris, Flammarion, 1979. * Harry Frankfurt, ''Démons, rêveurs et fous : la défense de la raison dans les Méditations de Descartes'', traduit de l'anglais, Paris, PUF, 1989 (édition originale 1970). * Jean-Luc Marion, ''Sur la théologie blanche de Descartes'', Paris, PUF, 1981. * Jean-Luc Marion, ''Questions cartésiennes'', 2 vol., Paris, PUF, 1991 et 1996. * Vincent Carraud, ''Causa sive ratio. La raison de la cause, de Suarez à Leibniz'', Paris, PUF, 2002. * Gary Hatfield, ''Descartes and the Meditations'', Londres, Routledge, 2003. * Desmond Clarke, ''Descartes's Theory of Mind'', Oxford, Oxford University Press, 2003. === Études sur la physique et la science cartésiennes === * Daniel Garber, ''La Physique métaphysique de Descartes'', traduit de l'anglais, Paris, PUF, 1999 (édition originale Chicago, 1992). * Daniel Garber, ''Descartes Embodied: Reading Cartesian Philosophy through Cartesian Science'', Cambridge, Cambridge University Press, 2001. * Desmond Clarke, ''Descartes' Philosophy of Science'', Manchester, Manchester University Press, 1982. * Stephen Gaukroger, John Schuster et John Sutton (dir.), ''Descartes' Natural Philosophy'', Londres et New York, Routledge, 2000. * A. I. Sabra, ''Theories of Light from Descartes to Newton'', Cambridge, Cambridge University Press, 1981 (première édition 1967). * Tarek R. Dika, ''Descartes's Method: The Formation of the Subject of Science'', Oxford, Oxford University Press, 2023. === Études sur la morale, l'anthropologie et les passions === * Geneviève Rodis-Lewis, ''La Morale de Descartes'', Paris, PUF, 1957. * Denis Kambouchner, ''L'Homme des passions. Commentaires sur Descartes'', 2 vol., Paris, Albin Michel, 1995. * Denis Kambouchner, ''Descartes et la philosophie morale'', Paris, Hermann, 2008. === Études sur le cartésianisme et sa réception === * Tad M. Schmaltz, ''Early Modern Cartesianisms : Dutch and French Constructions'', New York, Oxford University Press, 2017. * Steven Nadler, ''The Cambridge Companion to Malebranche'', Cambridge, Cambridge University Press, 2000. * Theo Verbeek, ''Descartes and the Dutch : Early Reactions to Cartesian Philosophy, 1637-1650'', Carbondale, Southern Illinois University Press, 1992. === Ouvrages de référence === * Frédéric de Buzon et Denis Kambouchner, ''Le Vocabulaire de Descartes'', Paris, Ellipses, 2002 (nouvelle édition 2011). * Jean-François Pradeau (dir.), ''Histoire de la philosophie'', Paris, Seuil, 2009. === Sources textuelles citées dans cet article === * ''Discours de la méthode'' (1637), parties III à V : morale par provision, métaphysique du ''cogito'', physique et physiologie. AT VI. * ''Méditations métaphysiques'' (1641, 1642 ; trad. fr. 1647), I à VI. AT VII (latin) ; AT IX-1 (français). * ''Principes de la philosophie'' (1644 ; trad. fr. 1647), première partie, art. 7-10, 22-24, 39, et passim ; deuxième partie, art. 37-40 (lois de la nature et principe d'inertie). AT VIII-1 ; AT IX-2. * ''Les Passions de l'âme'' (1649), première et troisième parties, en particulier art. 27, 30, 153, 161. AT XI. * ''Notae in programma'' (1648), réponse au placard de Henricus Regius. AT VIII-2. * Lettre à Mersenne du 15 avril 1630 (libre création des vérités éternelles), AT I, p. 145-146. * Lettre à Mersenne du 6 mai 1630 et du 27 mai 1630 (incompréhensibilité de Dieu et vérités éternelles), AT I, p. 149-153. * Lettres à la princesse Élisabeth de Bohême, mai-juin 1643 (notions primitives et union de l'âme et du corps) ; août-septembre 1645 (morale et béatitude), AT III et IV. * Lettre à Henry More du 5 février 1649 (statut de la pensée chez les animaux), AT V, p. 276-277. * Lettres à Denis Mesland, 2 mai 1644 et 9 février 1645 (libre arbitre, vérités éternelles, eucharistie), AT IV. {{DEFAULTSORT:Descartes}} [[Catégorie:Philosophe]] ez6ibc8em758whnci1oroq5agto1x9i Discussion utilisateur:Jean-Jacques MILAN/Livre d'or 3 83942 768487 768091 2026-06-24T15:14:20Z Fourmidable 92370 /* Messages d'utilisateurs */ 768487 wikitext text/x-wiki Jean-Jacques Milan est mort le {{Date-|12|4|2026}}. Il était passionné de photo et contributeur sur 3 gros projets. Il totalisait {{unité|80000 contributions}} à travers les projets. == Messages d'utilisateurs == <gallery> File:Candleburning.jpg|Condoléances à Élise, sa femme, et toute sa famille. [[user:Eihel|Eihel]] File:Candleburning.jpg|--~~~~ </gallery> ---- On a eu des débuts un peu rugueux ^^ (les torts étaient partagés) mais au final, on s'appréciait mutuellement... [[Utilisateur:Cdang|Cdang]] ([[Discussion utilisateur:Cdang|discussion]]) 15 juin 2026 à 01:31 (CEST) ---- Sincères condoléances. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 15 juin 2026 à 16:21 (CEST) ---- Condoléances à tous ceux qui le connaissait. Nous avons perdu un contributeur important : en plus d'avoir créé [[Photographie|le plus grand livre du projet]], il a mis en place et développé [[Wikilivres:CDU|le système de classement des livres]]. --&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 18 juin 2026 à 21:09 (CEST) 5mncp3nq4h23t3udp22wullg1ldkqx8 768488 768487 2026-06-24T15:14:39Z Fourmidable 92370 /* Messages d'utilisateurs */ 768488 wikitext text/x-wiki Jean-Jacques Milan est mort le {{Date-|12|4|2026}}. Il était passionné de photo et contributeur sur 3 gros projets. Il totalisait {{unité|80000 contributions}} à travers les projets. == Messages d'utilisateurs == <gallery> File:Candleburning.jpg|Condoléances à Élise, sa femme, et toute sa famille. [[user:Eihel|Eihel]] File:Candleburning.jpg|[[Utilisateur:Fourmidable|Fourmidable]] </gallery> ---- On a eu des débuts un peu rugueux ^^ (les torts étaient partagés) mais au final, on s'appréciait mutuellement... [[Utilisateur:Cdang|Cdang]] ([[Discussion utilisateur:Cdang|discussion]]) 15 juin 2026 à 01:31 (CEST) ---- Sincères condoléances. [[Utilisateur:JackPotte|JackPotte]] ([[Discussion utilisateur:JackPotte|<span style="color:#FF6600">$</span>♠]]) 15 juin 2026 à 16:21 (CEST) ---- Condoléances à tous ceux qui le connaissait. Nous avons perdu un contributeur important : en plus d'avoir créé [[Photographie|le plus grand livre du projet]], il a mis en place et développé [[Wikilivres:CDU|le système de classement des livres]]. --&nbsp;◄&nbsp;[[Utilisateur:DavidL|'''D'''avid&nbsp;'''L''']]&nbsp;•&nbsp;[[Discussion Utilisateur:DavidL|discuter]]&nbsp;► 18 juin 2026 à 21:09 (CEST) 2ug7zyy4a0e5nx4nt7w28t44vk8idbn Mathc matrices/09b 0 83964 768516 768474 2026-06-24T17:28:21Z Xhungab 23827 768516 wikitext text/x-wiki __NOTOC__ [[Catégorie:Mathc matrices (livre)]] [[Mathc_matrices/Sommaire| Sommaire]] {{Partie{{{type|}}}| '''Système dynamique linéaire discret .'''}} Soit : u_n+1 = a11 u_n + a12 v_n v_n+1 = a21 u_n + a22 v_n Que l'on peu écrire : (t : transposé) X_n = [u_n, v_n]t et alors X_n+1 = A X_n X_n+1 = [u_n+1,v_n+1]t Posons X_0 = [u_0,v_0]t Alors X_1 = A X_0 X_2 = A X_1 = A^2 X_0 X_3 = A X_2 = A^3 X_0 ... X_n = A^n X_0 Calculez : lim X_n = n-> oo Pour cela nous allons utiliser les valeurs propres et les vecteurs propres pour la première méthode, et pour la seconde méthode nous calculerons directement la puissance de A. '''Exemple 1 :''' * [[Mathc matrices/097| c00a.c ]] : Calculons les vecteurs propres et les valeurs propres. * [[Mathc matrices/098| c00b.c ]] : Calculons A^P Nous pourrons observer qu'a partir de '''P = 19''', la valeur de '''A^P n'évolue plus'''. '''Exemple 2 :''' * [[Mathc matrices/099| c00c.c ]] : Calculons les vecteurs propres et les valeurs propres. * [[Mathc matrices/09a| c00d.c ]] Calculons A^n Nous pourrons observer qu'a partir de '''P = 17''', la valeur de '''A^P n'évolue plus'''. {{AutoCat}} s49j5w27xn5kpzjmpf744hpiaomo9g1